supabase-cli/internal/storage/ls/ls.go

137 lines
3.6 KiB
Go

package ls
import (
"context"
"fmt"
"os"
"path"
"strings"
"github.com/spf13/afero"
"github.com/supabase/cli/internal/storage/client"
"github.com/supabase/cli/internal/utils/flags"
"github.com/supabase/cli/pkg/storage"
)
func Run(ctx context.Context, objectPath string, recursive bool, fsys afero.Fs) error {
remotePath, err := client.ParseStorageURL(objectPath)
if err != nil {
return err
}
callback := func(objectPath string) error {
fmt.Println(objectPath)
return nil
}
api, err := client.NewStorageAPI(ctx, flags.ProjectRef)
if err != nil {
return err
}
if recursive {
return IterateStoragePathsAll(ctx, api, remotePath, callback)
}
return IterateStoragePaths(ctx, api, remotePath, callback)
}
func ListStoragePaths(ctx context.Context, api storage.StorageAPI, remotePath string) ([]string, error) {
var result []string
err := IterateStoragePaths(ctx, api, remotePath, func(objectName string) error {
result = append(result, objectName)
return nil
})
return result, err
}
func IterateStoragePaths(ctx context.Context, api storage.StorageAPI, remotePath string, callback func(objectName string) error) error {
bucket, prefix := client.SplitBucketPrefix(remotePath)
if len(bucket) == 0 || (len(prefix) == 0 && !strings.HasSuffix(remotePath, "/")) {
buckets, err := api.ListBuckets(ctx)
if err != nil {
return err
}
for _, b := range buckets {
if strings.HasPrefix(b.Name, bucket) {
if err := callback(b.Name + "/"); err != nil {
return err
}
}
}
} else {
pages := 1
for i := 0; i < pages; i++ {
objects, err := api.ListObjects(ctx, bucket, prefix, i)
if err != nil {
return err
}
for _, o := range objects {
name := o.Name
if o.Id == nil {
name += "/"
}
if err := callback(name); err != nil {
return err
}
}
if len(objects) == storage.PAGE_LIMIT {
// TODO: show interactive prompt?
fmt.Fprintln(os.Stderr, "Loading page:", pages)
pages++
}
}
}
return nil
}
// Expects remotePath to be terminated by "/"
func ListStoragePathsAll(ctx context.Context, api storage.StorageAPI, remotePath string) ([]string, error) {
var result []string
err := IterateStoragePathsAll(ctx, api, remotePath, func(objectPath string) error {
result = append(result, objectPath)
return nil
})
return result, err
}
func IterateStoragePathsAll(ctx context.Context, api storage.StorageAPI, remotePath string, callback func(objectPath string) error) error {
basePath := remotePath
if !strings.HasSuffix(remotePath, "/") {
basePath, _ = path.Split(remotePath)
}
// BFS so we can list paths in increasing depth
dirQueue := make([]string, 0)
// We don't know if user passed in a directory or file, so query storage first.
if err := IterateStoragePaths(ctx, api, remotePath, func(objectName string) error {
objectPath := basePath + objectName
if strings.HasSuffix(objectName, "/") {
dirQueue = append(dirQueue, objectPath)
return nil
}
return callback(objectPath)
}); err != nil {
return err
}
for len(dirQueue) > 0 {
dirPath := dirQueue[len(dirQueue)-1]
dirQueue = dirQueue[:len(dirQueue)-1]
empty := true
if err := IterateStoragePaths(ctx, api, dirPath, func(objectName string) error {
empty = false
objectPath := dirPath + objectName
if strings.HasSuffix(objectName, "/") {
dirQueue = append(dirQueue, objectPath)
return nil
}
return callback(objectPath)
}); err != nil {
return err
}
// Also report empty buckets
bucket, prefix := client.SplitBucketPrefix(dirPath)
if empty && len(prefix) == 0 {
if err := callback(bucket + "/"); err != nil {
return err
}
}
}
return nil
}