137 lines
3.6 KiB
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
|
|
}
|