Add oiUserMetadata to ObjectInfo to return user metadata (#132)

This commit is contained in:
Aditya Manthramurthy 2019-07-24 13:30:03 -07:00 committed by Krishnan Parthasarathi
parent 04d1193201
commit b39127778e
7 changed files with 68 additions and 26 deletions

View File

@ -6,6 +6,8 @@ Changelog
* Switch to faster map data type - all previous usage of * Switch to faster map data type - all previous usage of
Data.Map.Strict and Data.Set is replaced with Data.HashMap.Strict Data.Map.Strict and Data.Set is replaced with Data.HashMap.Strict
and Data.HashSet. and Data.HashSet.
* Add `oiUserMetadata` to parse and return user metadata stored with
an object.
## Version 1.4.0 ## Version 1.4.0

View File

@ -90,6 +90,7 @@ module Network.Minio
, oiModTime , oiModTime
, oiETag , oiETag
, oiSize , oiSize
, oiUserMetadata
, oiMetadata , oiMetadata
-- ** Listing incomplete uploads -- ** Listing incomplete uploads

View File

@ -335,14 +335,20 @@ data PutObjectOptions = PutObjectOptions {
defaultPutObjectOptions :: PutObjectOptions defaultPutObjectOptions :: PutObjectOptions
defaultPutObjectOptions = PutObjectOptions Nothing Nothing Nothing Nothing Nothing Nothing [] Nothing Nothing defaultPutObjectOptions = PutObjectOptions Nothing Nothing Nothing Nothing Nothing Nothing [] Nothing Nothing
isUserMetadataHeaderName :: Text -> Bool
isUserMetadataHeaderName k =
let prefix = T.toCaseFold "X-Amz-Meta-"
n = T.length prefix
in T.toCaseFold (T.take n k) == prefix
addXAmzMetaPrefix :: Text -> Text addXAmzMetaPrefix :: Text -> Text
addXAmzMetaPrefix s = do addXAmzMetaPrefix s =
if (T.isPrefixOf "x-amz-meta-" s) if isUserMetadataHeaderName s
then s then s
else T.concat ["x-amz-meta-", s] else "X-Amz-Meta-" <> s
mkHeaderFromMetadata :: [(Text, Text)] -> [HT.Header] mkHeaderFromMetadata :: [(Text, Text)] -> [HT.Header]
mkHeaderFromMetadata = map (\(x, y) -> (mk $ encodeUtf8 $ addXAmzMetaPrefix $ T.toLower x, encodeUtf8 y)) mkHeaderFromMetadata = map (\(x, y) -> (mk $ encodeUtf8 $ addXAmzMetaPrefix $ x, encodeUtf8 y))
pooToHeaders :: PutObjectOptions -> [HT.Header] pooToHeaders :: PutObjectOptions -> [HT.Header]
pooToHeaders poo = userMetadata pooToHeaders poo = userMetadata
@ -435,12 +441,19 @@ data ListObjectsV1Result = ListObjectsV1Result {
-- | Represents information about an object. -- | Represents information about an object.
data ObjectInfo = ObjectInfo data ObjectInfo = ObjectInfo
{ oiObject :: Object -- ^ Object key { oiObject :: Object -- ^ Object key
, oiModTime :: UTCTime -- ^ Mdification time of the object , oiModTime :: UTCTime -- ^ Modification time of the object
, oiETag :: ETag -- ^ ETag of the object , oiETag :: ETag -- ^ ETag of the object
, oiSize :: Int64 -- ^ Size of the object in bytes , oiSize :: Int64 -- ^ Size of the object in bytes
, oiMetadata :: H.HashMap Text Text -- ^ A map of the metadata , oiUserMetadata :: H.HashMap Text Text -- ^ A map of user-metadata
-- key-value pairs -- pairs stored with an
-- object (keys will not
-- have the @X-Amz-Meta-@
-- prefix).
, oiMetadata :: H.HashMap Text Text -- ^ A map of metadata
-- key-value pairs (not
-- including the
-- user-metadata pairs)
} deriving (Show, Eq) } deriving (Show, Eq)
-- | Represents source object in server-side copy object -- | Represents source object in server-side copy object

View File

@ -412,20 +412,27 @@ listIncompleteParts' bucket object uploadId maxParts partNumMarker = do
headObject :: Bucket -> Object -> [HT.Header] -> Minio ObjectInfo headObject :: Bucket -> Object -> [HT.Header] -> Minio ObjectInfo
headObject bucket object reqHeaders = do headObject bucket object reqHeaders = do
resp <- executeRequest $ defaultS3ReqInfo { riMethod = HT.methodHead resp <- executeRequest $ defaultS3ReqInfo { riMethod = HT.methodHead
, riBucket = Just bucket , riBucket = Just bucket
, riObject = Just object , riObject = Just object
, riHeaders = reqHeaders , riHeaders = reqHeaders
} }
let let
headers = NC.responseHeaders resp headers = NC.responseHeaders resp
modTime = getLastModifiedHeader headers modTime = getLastModifiedHeader headers
etag = getETagHeader headers etag = getETagHeader headers
size = getContentLength headers size = getContentLength headers
metadata = getMetadataMap headers metadataPairs = getMetadata headers
userMetadata = getUserMetadataMap metadataPairs
metadata = getNonUserMetadataMap metadataPairs
maybe (throwIO MErrVInvalidObjectInfoResponse) return $ maybe (throwIO MErrVInvalidObjectInfoResponse) return $
ObjectInfo <$> Just object <*> modTime <*> etag <*> size <*> Just metadata ObjectInfo <$> Just object
<*> modTime
<*> etag
<*> size
<*> Just userMetadata
<*> Just metadata
-- | Query the object store if a given bucket exists. -- | Query the object store if a given bucket exists.

View File

@ -103,10 +103,27 @@ getETagHeader :: [HT.Header] -> Maybe Text
getETagHeader hs = decodeUtf8Lenient <$> lookupHeader Hdr.hETag hs getETagHeader hs = decodeUtf8Lenient <$> lookupHeader Hdr.hETag hs
getMetadata :: [HT.Header] -> [(Text, Text)] getMetadata :: [HT.Header] -> [(Text, Text)]
getMetadata = map ((\(x, y) -> (decodeUtf8Lenient $ original x, decodeUtf8Lenient $ stripBS y))) getMetadata =
map ((\(x, y) -> (decodeUtf8Lenient $ original x, decodeUtf8Lenient $ stripBS y)))
getMetadataMap :: [HT.Header] -> H.HashMap Text Text toMaybeMetadataHeader :: (Text, Text) -> Maybe (Text, Text)
getMetadataMap hs = H.fromList (getMetadata hs) toMaybeMetadataHeader (k, v) =
let checkPrefix t = bool Nothing (Just t) $
isUserMetadataHeaderName t
in (, v) <$> checkPrefix k
getNonUserMetadataMap :: [(Text, Text)] -> H.HashMap Text Text
getNonUserMetadataMap = H.fromList
. filter ( not
. isUserMetadataHeaderName
. fst
)
-- | This function collects all headers starting with `x-amz-meta-`
-- and strips off this prefix, and returns a map.
getUserMetadataMap :: [(Text, Text)] -> H.HashMap Text Text
getUserMetadataMap = H.fromList
. mapMaybe toMaybeMetadataHeader
getLastModifiedHeader :: [HT.Header] -> Maybe UTCTime getLastModifiedHeader :: [HT.Header] -> Maybe UTCTime
getLastModifiedHeader hs = do getLastModifiedHeader hs = do

View File

@ -31,7 +31,7 @@ module Network.Minio.XmlParser
import qualified Data.ByteString.Lazy as LB import qualified Data.ByteString.Lazy as LB
import qualified Data.HashMap.Strict as H import qualified Data.HashMap.Strict as H
import Data.List (zip3, zip4, zip5) import Data.List (zip3, zip4, zip6)
import qualified Data.Text as T import qualified Data.Text as T
import Data.Text.Read (decimal) import Data.Text.Read (decimal)
import Data.Time import Data.Time
@ -52,8 +52,8 @@ s3TimeFormat = iso8601DateFormat $ Just "%T%QZ"
uncurry4 :: (a -> b -> c -> d -> e) -> (a, b, c, d) -> e uncurry4 :: (a -> b -> c -> d -> e) -> (a, b, c, d) -> e
uncurry4 f (a, b, c, d) = f a b c d uncurry4 f (a, b, c, d) = f a b c d
uncurry5 :: (a -> b -> c -> d -> e -> f) -> (a, b, c, d, e) -> f uncurry6 :: (a -> b -> c -> d -> e -> f -> g) -> (a, b, c, d, e, f) -> g
uncurry5 f (a, b, c, d, e) = f a b c d e uncurry6 f (a, b, c, d, e, g) = f a b c d e g
-- | Parse time strings from XML -- | Parse time strings from XML
parseS3XMLTime :: (MonadIO m) => Text -> m UTCTime parseS3XMLTime :: (MonadIO m) => Text -> m UTCTime
@ -149,7 +149,8 @@ parseListObjectsV1Response xmldata = do
sizes <- parseDecimals sizeStr sizes <- parseDecimals sizeStr
let let
objects = map (uncurry5 ObjectInfo) $ zip5 keys modTimes etags sizes (repeat H.empty) objects = map (uncurry6 ObjectInfo) $
zip6 keys modTimes etags sizes (repeat H.empty) (repeat H.empty)
return $ ListObjectsV1Result hasMore nextMarker objects prefixes return $ ListObjectsV1Result hasMore nextMarker objects prefixes
@ -178,7 +179,8 @@ parseListObjectsResponse xmldata = do
sizes <- parseDecimals sizeStr sizes <- parseDecimals sizeStr
let let
objects = map (uncurry5 ObjectInfo) $ zip5 keys modTimes etags sizes (repeat H.empty) objects = map (uncurry6 ObjectInfo) $
zip6 keys modTimes etags sizes (repeat H.empty) (repeat H.empty)
return $ ListObjectsResult hasMore nextToken objects prefixes return $ ListObjectsResult hasMore nextToken objects prefixes

View File

@ -128,7 +128,7 @@ testParseListObjectsResult = do
\</ListBucketResult>" \</ListBucketResult>"
expectedListResult = ListObjectsResult True (Just "opaque") [object1] [] expectedListResult = ListObjectsResult True (Just "opaque") [object1] []
object1 = ObjectInfo "my-image.jpg" modifiedTime1 "\"fba9dede5f27731c9771645a39863328\"" 434234 H.empty object1 = ObjectInfo "my-image.jpg" modifiedTime1 "\"fba9dede5f27731c9771645a39863328\"" 434234 H.empty H.empty
modifiedTime1 = flip UTCTime 64230 $ fromGregorian 2009 10 12 modifiedTime1 = flip UTCTime 64230 $ fromGregorian 2009 10 12
parsedListObjectsResult <- tryValidationErr $ runTestNS $ parseListObjectsResponse xmldata parsedListObjectsResult <- tryValidationErr $ runTestNS $ parseListObjectsResponse xmldata
@ -155,7 +155,7 @@ testParseListObjectsV1Result = do
\</ListBucketResult>" \</ListBucketResult>"
expectedListResult = ListObjectsV1Result True (Just "my-image1.jpg") [object1] [] expectedListResult = ListObjectsV1Result True (Just "my-image1.jpg") [object1] []
object1 = ObjectInfo "my-image.jpg" modifiedTime1 "\"fba9dede5f27731c9771645a39863328\"" 434234 H.empty object1 = ObjectInfo "my-image.jpg" modifiedTime1 "\"fba9dede5f27731c9771645a39863328\"" 434234 H.empty H.empty
modifiedTime1 = flip UTCTime 64230 $ fromGregorian 2009 10 12 modifiedTime1 = flip UTCTime 64230 $ fromGregorian 2009 10 12
parsedListObjectsV1Result <- tryValidationErr $ runTestNS $ parseListObjectsV1Response xmldata parsedListObjectsV1Result <- tryValidationErr $ runTestNS $ parseListObjectsV1Response xmldata