mirror of https://github.com/perkeep/perkeep.git
s3: improve bucket enumeration
Change-Id: I65ed4a6575250bb2005215df461ada64ac20e41e
This commit is contained in:
parent
da813ff7b6
commit
c83e29cfde
|
@ -136,28 +136,68 @@ type Item struct {
|
||||||
|
|
||||||
type listBucketResults struct {
|
type listBucketResults struct {
|
||||||
Contents []*Item
|
Contents []*Item
|
||||||
|
IsTruncated bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) ListBucket(bucket string, after string, maxKeys int) (items []*Item, reterr error) {
|
// marker returns the string lexically greater than the provided s,
|
||||||
|
// if s is not empty.
|
||||||
|
func marker(s string) string {
|
||||||
|
if s == "" {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
b := []byte(s)
|
||||||
|
i := len(b)
|
||||||
|
for i > 0 {
|
||||||
|
i--
|
||||||
|
b[i]++
|
||||||
|
if b[i] != 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListBucket returns 0 to maxKeys (inclusive) items from the provided
|
||||||
|
// bucket. The items will have keys greater than the provided after, which
|
||||||
|
// may be empty. (Note: this is not greater than or equal to, like the S3
|
||||||
|
// API's 'marker' parameter). If the length of the returned items is equal
|
||||||
|
// to maxKeys, there is no indication whether or not the returned list is
|
||||||
|
// truncated.
|
||||||
|
func (c *Client) ListBucket(bucket string, after string, maxKeys int) (items []*Item, err error) {
|
||||||
if maxKeys < 0 {
|
if maxKeys < 0 {
|
||||||
return nil, errors.New("invalid maxLeys")
|
return nil, errors.New("invalid negative maxKeys")
|
||||||
|
}
|
||||||
|
const s3APIMaxFetch = 1000
|
||||||
|
for len(items) < maxKeys {
|
||||||
|
fetchN := maxKeys - len(items)
|
||||||
|
if fetchN > s3APIMaxFetch {
|
||||||
|
fetchN = s3APIMaxFetch
|
||||||
}
|
}
|
||||||
var bres listBucketResults
|
var bres listBucketResults
|
||||||
url_ := fmt.Sprintf("http://%s.s3.amazonaws.com/?marker=%s&max-keys=%d",
|
url_ := fmt.Sprintf("http://%s.s3.amazonaws.com/?marker=%s&max-keys=%d",
|
||||||
bucket, url.QueryEscape(after), maxKeys)
|
bucket, url.QueryEscape(marker(after)), fetchN)
|
||||||
req := newReq(url_)
|
req := newReq(url_)
|
||||||
c.Auth.SignRequest(req)
|
c.Auth.SignRequest(req)
|
||||||
res, err := c.httpClient().Do(req)
|
res, err := c.httpClient().Do(req)
|
||||||
if res != nil && res.Body != nil {
|
|
||||||
defer res.Body.Close()
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := xml.NewDecoder(res.Body).Decode(&bres); err != nil {
|
if err := xml.NewDecoder(res.Body).Decode(&bres); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return bres.Contents, nil
|
res.Body.Close()
|
||||||
|
for _, it := range bres.Contents {
|
||||||
|
if it.Key <= after {
|
||||||
|
return nil, fmt.Errorf("Unexpected response from Amazon: item key %q but wanted greater than %q", it.Key, after)
|
||||||
|
}
|
||||||
|
items = append(items, it)
|
||||||
|
after = it.Key
|
||||||
|
}
|
||||||
|
if !bres.IsTruncated {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Get(bucket, key string) (body io.ReadCloser, size int64, err error) {
|
func (c *Client) Get(bucket, key string) (body io.ReadCloser, size int64, err error) {
|
||||||
|
|
|
@ -41,3 +41,19 @@ func TestBuckets(t *testing.T) {
|
||||||
}
|
}
|
||||||
tc.Buckets()
|
tc.Buckets()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMarker(t *testing.T) {
|
||||||
|
tests := []struct{
|
||||||
|
s, want string
|
||||||
|
}{
|
||||||
|
{"", ""},
|
||||||
|
{"abc", "abd"},
|
||||||
|
{"ab\xff", "ac\x00"},
|
||||||
|
{"a\xff\xff", "b\x00\x00"},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
if got := marker(tt.s); got != tt.want {
|
||||||
|
t.Errorf("marker(%q) = %q; want %q", tt.s, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue