Browse Source

add ListObjectsV2

taowei.wtw 5 years ago
parent
commit
c5ea259ccd
5 changed files with 209 additions and 2 deletions
  1. 29 1
      oss/bucket.go
  2. 105 0
      oss/bucket_test.go
  3. 1 1
      oss/const.go
  4. 26 0
      oss/option.go
  5. 48 0
      oss/type.go

+ 29 - 1
oss/bucket.go

@@ -576,7 +576,7 @@ func (bucket Bucket) IsObjectExist(objectKey string, options ...Option) (bool, e
 //
 //            For common usage scenario, check out sample/list_object.go.
 //
-// ListObjectsResponse    the return value after operation succeeds (only valid when error is nil).
+// ListObjectsResult    the return value after operation succeeds (only valid when error is nil).
 //
 func (bucket Bucket) ListObjects(options ...Option) (ListObjectsResult, error) {
 	var out ListObjectsResult
@@ -602,6 +602,34 @@ func (bucket Bucket) ListObjects(options ...Option) (ListObjectsResult, error) {
 	return out, err
 }
 
+// Recommend to use ListObjectsV2 to replace ListObjects
+// ListOListObjectsV2bjects lists the objects under the current bucket.
+// ListObjectsResultV2    the return value after operation succeeds (only valid when error is nil).
+func (bucket Bucket) ListObjectsV2(options ...Option) (ListObjectsResultV2, error) {
+	var out ListObjectsResultV2
+
+	options = append(options, EncodingType("url"))
+	options = append(options, ListType(2))
+	params, err := GetRawParams(options)
+	if err != nil {
+		return out, err
+	}
+
+	resp, err := bucket.do("GET", "", params, options, nil, nil)
+	if err != nil {
+		return out, err
+	}
+	defer resp.Body.Close()
+
+	err = xmlUnmarshal(resp.Body, &out)
+	if err != nil {
+		return out, err
+	}
+
+	err = decodeListObjectsResultV2(&out)
+	return out, err
+}
+
 // ListObjectVersions lists objects of all versions under the current bucket.
 func (bucket Bucket) ListObjectVersions(options ...Option) (ListObjectVersionsResult, error) {
 	var out ListObjectVersionsResult

+ 105 - 0
oss/bucket_test.go

@@ -1206,6 +1206,111 @@ func (s *OssBucketSuite) TestListObjects(c *C) {
 	c.Assert(err, IsNil)
 }
 
+// TestListObjects
+func (s *OssBucketSuite) TestListObjectsV2NotBatch(c *C) {
+	objectName := objectNamePrefix + RandStr(8)
+
+	// create a bucket with default proprety
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	bucketName := bucketNamePrefix + RandLowStr(6)
+	err = client.CreateBucket(bucketName)
+	c.Assert(err, IsNil)
+
+	bucket, err := client.Bucket(bucketName)
+
+	// List empty bucket
+	lor, err := bucket.ListObjectsV2(StartAfter(""))
+	c.Assert(err, IsNil)
+	left := len(lor.Objects)
+
+	// Put three objects
+	err = bucket.PutObject(objectName+"1", strings.NewReader(""))
+	c.Assert(err, IsNil)
+	err = bucket.PutObject(objectName+"2", strings.NewReader(""))
+	c.Assert(err, IsNil)
+	err = bucket.PutObject(objectName+"3", strings.NewReader(""))
+	c.Assert(err, IsNil)
+
+	// List
+	lor, err = bucket.ListObjectsV2(FetchOwner(true))
+	c.Assert(err, IsNil)
+	c.Assert(len(lor.Objects), Equals, left+3)
+	c.Assert(len(lor.Objects[0].Owner.ID) > 0, Equals, true)
+	c.Assert(len(lor.Objects[0].Owner.DisplayName) > 0, Equals, true)
+
+	// List with prefix
+	lor, err = bucket.ListObjectsV2(Prefix(objectName + "2"))
+	c.Assert(err, IsNil)
+	c.Assert(len(lor.Objects), Equals, 1)
+	c.Assert(lor.Objects[0].Key, Equals, objectName+"2")
+
+	lor, err = bucket.ListObjectsV2(Prefix(objectName + "22"))
+	c.Assert(err, IsNil)
+	c.Assert(len(lor.Objects), Equals, 0)
+
+	// List with max keys
+	lor, err = bucket.ListObjectsV2(Prefix(objectName), MaxKeys(2))
+	c.Assert(err, IsNil)
+	c.Assert(len(lor.Objects), Equals, 2)
+
+	// List with marker
+	lor, err = bucket.ListObjectsV2(StartAfter(objectName+"1"), MaxKeys(1))
+	c.Assert(err, IsNil)
+	c.Assert(len(lor.Objects), Equals, 1)
+	c.Assert(lor.IsTruncated, Equals, true)
+	c.Assert(len(lor.NextContinuationToken) > 0, Equals, true)
+	c.Assert(lor.Objects[0].Key, Equals, objectName+"2")
+
+	lor, err = bucket.ListObjectsV2(Prefix(objectName), StartAfter(objectName+"1"), MaxKeys(2))
+	c.Assert(err, IsNil)
+	c.Assert(len(lor.Objects), Equals, 2)
+	c.Assert(lor.IsTruncated, Equals, false)
+	c.Assert(lor.NextContinuationToken, Equals, "")
+	ForceDeleteBucket(client, bucketName, c)
+	c.Assert(lor.Objects[0].Key, Equals, objectName+"2")
+	c.Assert(lor.Objects[1].Key, Equals, objectName+"3")
+}
+
+// TestListObjects
+func (s *OssBucketSuite) TestListObjectsV2BatchList(c *C) {
+	// create a bucket with default proprety
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	bucketName := bucketNamePrefix + RandLowStr(6)
+	err = client.CreateBucket(bucketName)
+	c.Assert(err, IsNil)
+
+	bucket, err := client.Bucket(bucketName)
+
+	// Put three objects
+	count := 17
+	objectName := "testobject-" + RandLowStr(6)
+	for i := 0; i < count; i++ {
+		err = bucket.PutObject(objectName+strconv.Itoa(i), strings.NewReader(""))
+		c.Assert(err, IsNil)
+	}
+
+	Objects := []ObjectProperties{}
+
+	// List Object
+	continuationToken := ""
+	prefix := ""
+	for {
+		lor, err := bucket.ListObjectsV2(Prefix(prefix), ContinuationToken(continuationToken), MaxKeys(3))
+		c.Assert(err, IsNil)
+		Objects = append(Objects, lor.Objects...)
+		continuationToken = lor.NextContinuationToken
+		if !lor.IsTruncated {
+			break
+		}
+	}
+	c.Assert(len(Objects), Equals, count)
+	ForceDeleteBucket(client, bucketName, c)
+}
+
 // TestListObjects
 func (s *OssBucketSuite) TestListObjectsEncodingType(c *C) {
 	prefix := objectNamePrefix + "床前明月光,疑是地上霜。举头望明月,低头思故乡。"

+ 1 - 1
oss/const.go

@@ -224,7 +224,7 @@ const (
 
 	NullVersion = "null"
 
-	Version = "v2.1.4" // Go SDK version
+	Version = "v2.1.5" // Go SDK version
 )
 
 // FrameType

+ 26 - 0
oss/option.go

@@ -365,6 +365,32 @@ func Sequential() Option {
 	return addParam("sequential", "")
 }
 
+// ListType is an option to set List-type parameter for ListObjectsV2
+func ListType(value int) Option {
+	return addParam("list-type", strconv.Itoa(value))
+}
+
+// StartAfter is an option to set start-after parameter for ListObjectsV2
+func StartAfter(value string) Option {
+	return addParam("start-after", value)
+}
+
+// ContinuationToken is an option to set Continuation-token parameter for ListObjectsV2
+func ContinuationToken(value string) Option {
+	if value == "" {
+		return addParam("continuation-token", nil)
+	}
+	return addParam("continuation-token", value)
+}
+
+// FetchOwner is an option to set Fetch-owner parameter for ListObjectsV2
+func FetchOwner(value bool) Option {
+	if value {
+		return addParam("fetch-owner", "true")
+	}
+	return addParam("fetch-owner", "false")
+}
+
 // DeleteObjectsQuiet false:DeleteObjects in verbose mode; true:DeleteObjects in quite mode. Default is false.
 func DeleteObjectsQuiet(isQuiet bool) Option {
 	return addArg(deleteObjectsQuiet, isQuiet)

+ 48 - 0
oss/type.go

@@ -342,6 +342,20 @@ type ObjectProperties struct {
 	StorageClass string    `xml:"StorageClass"` // Object storage class (Standard, IA, Archive)
 }
 
+// ListObjectsResultV2 defines the result from ListObjectsV2 request
+type ListObjectsResultV2 struct {
+	XMLName               xml.Name           `xml:"ListBucketResult"`
+	Prefix                string             `xml:"Prefix"`                // The object prefix
+	StartAfter            string             `xml:"StartAfter"`            // the input StartAfter
+	ContinuationToken     string             `xml:"ContinuationToken"`     // the input ContinuationToken
+	MaxKeys               int                `xml:"MaxKeys"`               // Max keys to return
+	Delimiter             string             `xml:"Delimiter"`             // The delimiter for grouping objects' name
+	IsTruncated           bool               `xml:"IsTruncated"`           // Flag indicates if all results are returned (when it's false)
+	NextContinuationToken string             `xml:"NextContinuationToken"` // The start point of the next NextContinuationToken
+	Objects               []ObjectProperties `xml:"Contents"`              // Object list
+	CommonPrefixes        []string           `xml:"CommonPrefixes>Prefix"` // You can think of commonprefixes as "folders" whose names end with the delimiter
+}
+
 // ListObjectVersionsResult defines the result from ListObjectVersions request
 type ListObjectVersionsResult struct {
 	XMLName             xml.Name                       `xml:"ListVersionsResult"`
@@ -582,6 +596,40 @@ func decodeListObjectsResult(result *ListObjectsResult) error {
 	return nil
 }
 
+// decodeListObjectsResult decodes list objects result in URL encoding
+func decodeListObjectsResultV2(result *ListObjectsResultV2) error {
+	var err error
+	result.Prefix, err = url.QueryUnescape(result.Prefix)
+	if err != nil {
+		return err
+	}
+	result.StartAfter, err = url.QueryUnescape(result.StartAfter)
+	if err != nil {
+		return err
+	}
+	result.Delimiter, err = url.QueryUnescape(result.Delimiter)
+	if err != nil {
+		return err
+	}
+	result.NextContinuationToken, err = url.QueryUnescape(result.NextContinuationToken)
+	if err != nil {
+		return err
+	}
+	for i := 0; i < len(result.Objects); i++ {
+		result.Objects[i].Key, err = url.QueryUnescape(result.Objects[i].Key)
+		if err != nil {
+			return err
+		}
+	}
+	for i := 0; i < len(result.CommonPrefixes); i++ {
+		result.CommonPrefixes[i], err = url.QueryUnescape(result.CommonPrefixes[i])
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
 // decodeListObjectVersionsResult decodes list version objects result in URL encoding
 func decodeListObjectVersionsResult(result *ListObjectVersionsResult) error {
 	var err error