summaryrefslogtreecommitdiff
path: root/vendor/github.com/minio/minio-go/v7/api-list.go
diff options
context:
space:
mode:
authorLibravatar kim <grufwub@gmail.com>2025-05-22 16:27:55 +0200
committerLibravatar kim <gruf@noreply.codeberg.org>2025-05-22 16:27:55 +0200
commitb6ff55662e0281c0d6e111f9307625ef695df2fa (patch)
tree5f7761efa0b51a7a7d56f96fce3681c8e9b66fe9 /vendor/github.com/minio/minio-go/v7/api-list.go
parent[chore/woodpecker] don't make `test` depend on `lint` (#4189) (diff)
downloadgotosocial-b6ff55662e0281c0d6e111f9307625ef695df2fa.tar.xz
[chore] update dependencies (#4188)
Update dependencies: - github.com/gin-gonic/gin v1.10.0 -> v1.10.1 - github.com/gin-contrib/sessions v1.10.3 -> v1.10.4 - github.com/jackc/pgx/v5 v5.7.4 -> v5.7.5 - github.com/minio/minio-go/v7 v7.0.91 -> v7.0.92 - github.com/pquerna/otp v1.4.0 -> v1.5.0 - github.com/tdewolff/minify/v2 v2.23.5 -> v2.23.8 - github.com/yuin/goldmark v1.7.11 -> v1.7.12 - go.opentelemetry.io/otel{,/*} v1.35.0 -> v1.36.0 - modernc.org/sqlite v1.37.0 -> v1.37.1 Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4188 Reviewed-by: Daenney <daenney@noreply.codeberg.org> Co-authored-by: kim <grufwub@gmail.com> Co-committed-by: kim <grufwub@gmail.com>
Diffstat (limited to 'vendor/github.com/minio/minio-go/v7/api-list.go')
-rw-r--r--vendor/github.com/minio/minio-go/v7/api-list.go358
1 files changed, 195 insertions, 163 deletions
diff --git a/vendor/github.com/minio/minio-go/v7/api-list.go b/vendor/github.com/minio/minio-go/v7/api-list.go
index 26d35c4c2..1af0fadbf 100644
--- a/vendor/github.com/minio/minio-go/v7/api-list.go
+++ b/vendor/github.com/minio/minio-go/v7/api-list.go
@@ -20,6 +20,7 @@ package minio
import (
"context"
"fmt"
+ "iter"
"net/http"
"net/url"
"slices"
@@ -57,10 +58,66 @@ func (c *Client) ListBuckets(ctx context.Context) ([]BucketInfo, error) {
return listAllMyBucketsResult.Buckets.Bucket, nil
}
+// ListDirectoryBuckets list all buckets owned by this authenticated user.
+//
+// This call requires explicit authentication, no anonymous requests are
+// allowed for listing buckets.
+//
+// api := client.New(....)
+// dirBuckets, err := api.ListDirectoryBuckets(context.Background())
+func (c *Client) ListDirectoryBuckets(ctx context.Context) (iter.Seq2[BucketInfo, error], error) {
+ fetchBuckets := func(continuationToken string) ([]BucketInfo, string, error) {
+ metadata := requestMetadata{contentSHA256Hex: emptySHA256Hex}
+ metadata.queryValues = url.Values{}
+ metadata.queryValues.Set("max-directory-buckets", "1000")
+ if continuationToken != "" {
+ metadata.queryValues.Set("continuation-token", continuationToken)
+ }
+
+ // Execute GET on service.
+ resp, err := c.executeMethod(ctx, http.MethodGet, metadata)
+ defer closeResponse(resp)
+ if err != nil {
+ return nil, "", err
+ }
+ if resp != nil {
+ if resp.StatusCode != http.StatusOK {
+ return nil, "", httpRespToErrorResponse(resp, "", "")
+ }
+ }
+
+ results := listAllMyDirectoryBucketsResult{}
+ if err = xmlDecoder(resp.Body, &results); err != nil {
+ return nil, "", err
+ }
+
+ return results.Buckets.Bucket, results.ContinuationToken, nil
+ }
+
+ return func(yield func(BucketInfo, error) bool) {
+ var continuationToken string
+ for {
+ buckets, token, err := fetchBuckets(continuationToken)
+ if err != nil {
+ yield(BucketInfo{}, err)
+ return
+ }
+ for _, bucket := range buckets {
+ if !yield(bucket, nil) {
+ return
+ }
+ }
+ if token == "" {
+ // nothing to continue
+ return
+ }
+ continuationToken = token
+ }
+ }, nil
+}
+
// Bucket List Operations.
-func (c *Client) listObjectsV2(ctx context.Context, bucketName string, opts ListObjectsOptions) <-chan ObjectInfo {
- // Allocate new list objects channel.
- objectStatCh := make(chan ObjectInfo, 1)
+func (c *Client) listObjectsV2(ctx context.Context, bucketName string, opts ListObjectsOptions) iter.Seq[ObjectInfo] {
// Default listing is delimited at "/"
delimiter := "/"
if opts.Recursive {
@@ -71,63 +128,42 @@ func (c *Client) listObjectsV2(ctx context.Context, bucketName string, opts List
// Return object owner information by default
fetchOwner := true
- sendObjectInfo := func(info ObjectInfo) {
- select {
- case objectStatCh <- info:
- case <-ctx.Done():
+ return func(yield func(ObjectInfo) bool) {
+ if contextCanceled(ctx) {
+ return
}
- }
- // Validate bucket name.
- if err := s3utils.CheckValidBucketName(bucketName); err != nil {
- defer close(objectStatCh)
- sendObjectInfo(ObjectInfo{
- Err: err,
- })
- return objectStatCh
- }
-
- // Validate incoming object prefix.
- if err := s3utils.CheckValidObjectNamePrefix(opts.Prefix); err != nil {
- defer close(objectStatCh)
- sendObjectInfo(ObjectInfo{
- Err: err,
- })
- return objectStatCh
- }
+ // Validate bucket name.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ yield(ObjectInfo{Err: err})
+ return
+ }
- // Initiate list objects goroutine here.
- go func(objectStatCh chan<- ObjectInfo) {
- defer func() {
- if contextCanceled(ctx) {
- objectStatCh <- ObjectInfo{
- Err: ctx.Err(),
- }
- }
- close(objectStatCh)
- }()
+ // Validate incoming object prefix.
+ if err := s3utils.CheckValidObjectNamePrefix(opts.Prefix); err != nil {
+ yield(ObjectInfo{Err: err})
+ return
+ }
// Save continuationToken for next request.
var continuationToken string
for {
+ if contextCanceled(ctx) {
+ return
+ }
+
// Get list of objects a maximum of 1000 per request.
result, err := c.listObjectsV2Query(ctx, bucketName, opts.Prefix, continuationToken,
fetchOwner, opts.WithMetadata, delimiter, opts.StartAfter, opts.MaxKeys, opts.headers)
if err != nil {
- sendObjectInfo(ObjectInfo{
- Err: err,
- })
+ yield(ObjectInfo{Err: err})
return
}
// If contents are available loop through and send over channel.
for _, object := range result.Contents {
object.ETag = trimEtag(object.ETag)
- select {
- // Send object content.
- case objectStatCh <- object:
- // If receives done from the caller, return here.
- case <-ctx.Done():
+ if !yield(object) {
return
}
}
@@ -135,11 +171,7 @@ func (c *Client) listObjectsV2(ctx context.Context, bucketName string, opts List
// Send all common prefixes if any.
// NOTE: prefixes are only present if the request is delimited.
for _, obj := range result.CommonPrefixes {
- select {
- // Send object prefixes.
- case objectStatCh <- ObjectInfo{Key: obj.Prefix}:
- // If receives done from the caller, return here.
- case <-ctx.Done():
+ if !yield(ObjectInfo{Key: obj.Prefix}) {
return
}
}
@@ -156,14 +188,14 @@ func (c *Client) listObjectsV2(ctx context.Context, bucketName string, opts List
// Add this to catch broken S3 API implementations.
if continuationToken == "" {
- sendObjectInfo(ObjectInfo{
- Err: fmt.Errorf("listObjectsV2 is truncated without continuationToken, %s S3 server is incompatible with S3 API", c.endpointURL),
- })
- return
+ if !yield(ObjectInfo{
+ Err: fmt.Errorf("listObjectsV2 is truncated without continuationToken, %s S3 server is buggy", c.endpointURL),
+ }) {
+ return
+ }
}
}
- }(objectStatCh)
- return objectStatCh
+ }
}
// listObjectsV2Query - (List Objects V2) - List some or all (up to 1000) of the objects in a bucket.
@@ -277,9 +309,7 @@ func (c *Client) listObjectsV2Query(ctx context.Context, bucketName, objectPrefi
return listBucketResult, nil
}
-func (c *Client) listObjects(ctx context.Context, bucketName string, opts ListObjectsOptions) <-chan ObjectInfo {
- // Allocate new list objects channel.
- objectStatCh := make(chan ObjectInfo, 1)
+func (c *Client) listObjects(ctx context.Context, bucketName string, opts ListObjectsOptions) iter.Seq[ObjectInfo] {
// Default listing is delimited at "/"
delimiter := "/"
if opts.Recursive {
@@ -287,49 +317,33 @@ func (c *Client) listObjects(ctx context.Context, bucketName string, opts ListOb
delimiter = ""
}
- sendObjectInfo := func(info ObjectInfo) {
- select {
- case objectStatCh <- info:
- case <-ctx.Done():
+ return func(yield func(ObjectInfo) bool) {
+ if contextCanceled(ctx) {
+ return
}
- }
- // Validate bucket name.
- if err := s3utils.CheckValidBucketName(bucketName); err != nil {
- defer close(objectStatCh)
- sendObjectInfo(ObjectInfo{
- Err: err,
- })
- return objectStatCh
- }
- // Validate incoming object prefix.
- if err := s3utils.CheckValidObjectNamePrefix(opts.Prefix); err != nil {
- defer close(objectStatCh)
- sendObjectInfo(ObjectInfo{
- Err: err,
- })
- return objectStatCh
- }
+ // Validate bucket name.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ yield(ObjectInfo{Err: err})
+ return
+ }
- // Initiate list objects goroutine here.
- go func(objectStatCh chan<- ObjectInfo) {
- defer func() {
- if contextCanceled(ctx) {
- objectStatCh <- ObjectInfo{
- Err: ctx.Err(),
- }
- }
- close(objectStatCh)
- }()
+ // Validate incoming object prefix.
+ if err := s3utils.CheckValidObjectNamePrefix(opts.Prefix); err != nil {
+ yield(ObjectInfo{Err: err})
+ return
+ }
marker := opts.StartAfter
for {
+ if contextCanceled(ctx) {
+ return
+ }
+
// Get list of objects a maximum of 1000 per request.
result, err := c.listObjectsQuery(ctx, bucketName, opts.Prefix, marker, delimiter, opts.MaxKeys, opts.headers)
if err != nil {
- sendObjectInfo(ObjectInfo{
- Err: err,
- })
+ yield(ObjectInfo{Err: err})
return
}
@@ -338,11 +352,7 @@ func (c *Client) listObjects(ctx context.Context, bucketName string, opts ListOb
// Save the marker.
marker = object.Key
object.ETag = trimEtag(object.ETag)
- select {
- // Send object content.
- case objectStatCh <- object:
- // If receives done from the caller, return here.
- case <-ctx.Done():
+ if !yield(object) {
return
}
}
@@ -350,11 +360,7 @@ func (c *Client) listObjects(ctx context.Context, bucketName string, opts ListOb
// Send all common prefixes if any.
// NOTE: prefixes are only present if the request is delimited.
for _, obj := range result.CommonPrefixes {
- select {
- // Send object prefixes.
- case objectStatCh <- ObjectInfo{Key: obj.Prefix}:
- // If receives done from the caller, return here.
- case <-ctx.Done():
+ if !yield(ObjectInfo{Key: obj.Prefix}) {
return
}
}
@@ -369,13 +375,10 @@ func (c *Client) listObjects(ctx context.Context, bucketName string, opts ListOb
return
}
}
- }(objectStatCh)
- return objectStatCh
+ }
}
-func (c *Client) listObjectVersions(ctx context.Context, bucketName string, opts ListObjectsOptions) <-chan ObjectInfo {
- // Allocate new list objects channel.
- resultCh := make(chan ObjectInfo, 1)
+func (c *Client) listObjectVersions(ctx context.Context, bucketName string, opts ListObjectsOptions) iter.Seq[ObjectInfo] {
// Default listing is delimited at "/"
delimiter := "/"
if opts.Recursive {
@@ -383,41 +386,22 @@ func (c *Client) listObjectVersions(ctx context.Context, bucketName string, opts
delimiter = ""
}
- sendObjectInfo := func(info ObjectInfo) {
- select {
- case resultCh <- info:
- case <-ctx.Done():
+ return func(yield func(ObjectInfo) bool) {
+ if contextCanceled(ctx) {
+ return
}
- }
- // Validate bucket name.
- if err := s3utils.CheckValidBucketName(bucketName); err != nil {
- defer close(resultCh)
- sendObjectInfo(ObjectInfo{
- Err: err,
- })
- return resultCh
- }
-
- // Validate incoming object prefix.
- if err := s3utils.CheckValidObjectNamePrefix(opts.Prefix); err != nil {
- defer close(resultCh)
- sendObjectInfo(ObjectInfo{
- Err: err,
- })
- return resultCh
- }
+ // Validate bucket name.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ yield(ObjectInfo{Err: err})
+ return
+ }
- // Initiate list objects goroutine here.
- go func(resultCh chan<- ObjectInfo) {
- defer func() {
- if contextCanceled(ctx) {
- resultCh <- ObjectInfo{
- Err: ctx.Err(),
- }
- }
- close(resultCh)
- }()
+ // Validate incoming object prefix.
+ if err := s3utils.CheckValidObjectNamePrefix(opts.Prefix); err != nil {
+ yield(ObjectInfo{Err: err})
+ return
+ }
var (
keyMarker = ""
@@ -427,7 +411,8 @@ func (c *Client) listObjectVersions(ctx context.Context, bucketName string, opts
perVersions []Version
numVersions int
)
- send := func(vers []Version) {
+
+ send := func(vers []Version) bool {
if opts.WithVersions && opts.ReverseVersions {
slices.Reverse(vers)
numVersions = len(vers)
@@ -448,24 +433,24 @@ func (c *Client) listObjectVersions(ctx context.Context, bucketName string, opts
Internal: version.Internal,
NumVersions: numVersions,
}
- select {
- // Send object version info.
- case resultCh <- info:
- // If receives done from the caller, return here.
- case <-ctx.Done():
- return
+ if !yield(info) {
+ return false
}
}
+ return true
}
for {
+ if contextCanceled(ctx) {
+ return
+ }
+
// Get list of objects a maximum of 1000 per request.
result, err := c.listObjectVersionsQuery(ctx, bucketName, opts, keyMarker, versionIDMarker, delimiter)
if err != nil {
- sendObjectInfo(ObjectInfo{
- Err: err,
- })
+ yield(ObjectInfo{Err: err})
return
}
+
if opts.WithVersions && opts.ReverseVersions {
for _, version := range result.Versions {
if preName == "" {
@@ -479,24 +464,24 @@ func (c *Client) listObjectVersions(ctx context.Context, bucketName string, opts
continue
}
// Send the file versions.
- send(perVersions)
+ if !send(perVersions) {
+ return
+ }
perVersions = perVersions[:0]
perVersions = append(perVersions, version)
preName = result.Name
preKey = version.Key
}
} else {
- send(result.Versions)
+ if !send(result.Versions) {
+ return
+ }
}
// Send all common prefixes if any.
// NOTE: prefixes are only present if the request is delimited.
for _, obj := range result.CommonPrefixes {
- select {
- // Send object prefixes.
- case resultCh <- ObjectInfo{Key: obj.Prefix}:
- // If receives done from the caller, return here.
- case <-ctx.Done():
+ if !yield(ObjectInfo{Key: obj.Prefix}) {
return
}
}
@@ -511,22 +496,18 @@ func (c *Client) listObjectVersions(ctx context.Context, bucketName string, opts
versionIDMarker = result.NextVersionIDMarker
}
- // If context is canceled, return here.
- if contextCanceled(ctx) {
- return
- }
-
// Listing ends result is not truncated, return right here.
if !result.IsTruncated {
// sent the lasted file with versions
if opts.ReverseVersions && len(perVersions) > 0 {
- send(perVersions)
+ if !send(perVersions) {
+ return
+ }
}
return
}
}
- }(resultCh)
- return resultCh
+ }
}
// listObjectVersions - (List Object Versions) - List some or all (up to 1000) of the existing objects
@@ -769,6 +750,57 @@ func (o *ListObjectsOptions) Set(key, value string) {
// caller must drain the channel entirely and wait until channel is closed before proceeding, without
// waiting on the channel to be closed completely you might leak goroutines.
func (c *Client) ListObjects(ctx context.Context, bucketName string, opts ListObjectsOptions) <-chan ObjectInfo {
+ objectStatCh := make(chan ObjectInfo, 1)
+ go func() {
+ defer close(objectStatCh)
+ send := func(obj ObjectInfo) bool {
+ select {
+ case <-ctx.Done():
+ return false
+ case objectStatCh <- obj:
+ return true
+ }
+ }
+
+ var objIter iter.Seq[ObjectInfo]
+ switch {
+ case opts.WithVersions:
+ objIter = c.listObjectVersions(ctx, bucketName, opts)
+ case opts.UseV1:
+ objIter = c.listObjects(ctx, bucketName, opts)
+ default:
+ location, _ := c.bucketLocCache.Get(bucketName)
+ if location == "snowball" {
+ objIter = c.listObjects(ctx, bucketName, opts)
+ } else {
+ objIter = c.listObjectsV2(ctx, bucketName, opts)
+ }
+ }
+ for obj := range objIter {
+ if !send(obj) {
+ return
+ }
+ }
+ }()
+ return objectStatCh
+}
+
+// ListObjectsIter returns object list as a iterator sequence.
+// caller must cancel the context if they are not interested in
+// iterating further, if no more entries the iterator will
+// automatically stop.
+//
+// api := client.New(....)
+// for object := range api.ListObjectsIter(ctx, "mytestbucket", minio.ListObjectsOptions{Prefix: "starthere", Recursive:true}) {
+// if object.Err != nil {
+// // handle the errors.
+// }
+// fmt.Println(object)
+// }
+//
+// Canceling the context the iterator will stop, if you wish to discard the yielding make sure
+// to cancel the passed context without that you might leak coroutines
+func (c *Client) ListObjectsIter(ctx context.Context, bucketName string, opts ListObjectsOptions) iter.Seq[ObjectInfo] {
if opts.WithVersions {
return c.listObjectVersions(ctx, bucketName, opts)
}