github.com/supabase/cli@v1.168.1/internal/storage/ls/ls.go (about) 1 package ls 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "path" 8 "strings" 9 10 "github.com/spf13/afero" 11 "github.com/supabase/cli/internal/storage/client" 12 "github.com/supabase/cli/internal/utils/flags" 13 "github.com/supabase/cli/pkg/storage" 14 ) 15 16 func Run(ctx context.Context, objectPath string, recursive bool, fsys afero.Fs) error { 17 remotePath, err := client.ParseStorageURL(objectPath) 18 if err != nil { 19 return err 20 } 21 callback := func(objectPath string) error { 22 fmt.Println(objectPath) 23 return nil 24 } 25 api, err := client.NewStorageAPI(ctx, flags.ProjectRef) 26 if err != nil { 27 return err 28 } 29 if recursive { 30 return IterateStoragePathsAll(ctx, api, remotePath, callback) 31 } 32 return IterateStoragePaths(ctx, api, remotePath, callback) 33 } 34 35 func ListStoragePaths(ctx context.Context, api storage.StorageAPI, remotePath string) ([]string, error) { 36 var result []string 37 err := IterateStoragePaths(ctx, api, remotePath, func(objectName string) error { 38 result = append(result, objectName) 39 return nil 40 }) 41 return result, err 42 } 43 44 func IterateStoragePaths(ctx context.Context, api storage.StorageAPI, remotePath string, callback func(objectName string) error) error { 45 bucket, prefix := client.SplitBucketPrefix(remotePath) 46 if len(bucket) == 0 || (len(prefix) == 0 && !strings.HasSuffix(remotePath, "/")) { 47 buckets, err := api.ListBuckets(ctx) 48 if err != nil { 49 return err 50 } 51 for _, b := range buckets { 52 if strings.HasPrefix(b.Name, bucket) { 53 if err := callback(b.Name + "/"); err != nil { 54 return err 55 } 56 } 57 } 58 } else { 59 pages := 1 60 for i := 0; i < pages; i++ { 61 objects, err := api.ListObjects(ctx, bucket, prefix, i) 62 if err != nil { 63 return err 64 } 65 for _, o := range objects { 66 name := o.Name 67 if o.Id == nil { 68 name += "/" 69 } 70 if err := callback(name); err != nil { 71 return err 72 } 73 } 74 if len(objects) == storage.PAGE_LIMIT { 75 // TODO: show interactive prompt? 76 fmt.Fprintln(os.Stderr, "Loading page:", pages) 77 pages++ 78 } 79 } 80 } 81 return nil 82 } 83 84 // Expects remotePath to be terminated by "/" 85 func ListStoragePathsAll(ctx context.Context, api storage.StorageAPI, remotePath string) ([]string, error) { 86 var result []string 87 err := IterateStoragePathsAll(ctx, api, remotePath, func(objectPath string) error { 88 result = append(result, objectPath) 89 return nil 90 }) 91 return result, err 92 } 93 94 func IterateStoragePathsAll(ctx context.Context, api storage.StorageAPI, remotePath string, callback func(objectPath string) error) error { 95 basePath := remotePath 96 if !strings.HasSuffix(remotePath, "/") { 97 basePath, _ = path.Split(remotePath) 98 } 99 // BFS so we can list paths in increasing depth 100 dirQueue := make([]string, 0) 101 // We don't know if user passed in a directory or file, so query storage first. 102 if err := IterateStoragePaths(ctx, api, remotePath, func(objectName string) error { 103 objectPath := basePath + objectName 104 if strings.HasSuffix(objectName, "/") { 105 dirQueue = append(dirQueue, objectPath) 106 return nil 107 } 108 return callback(objectPath) 109 }); err != nil { 110 return err 111 } 112 for len(dirQueue) > 0 { 113 dirPath := dirQueue[len(dirQueue)-1] 114 dirQueue = dirQueue[:len(dirQueue)-1] 115 empty := true 116 if err := IterateStoragePaths(ctx, api, dirPath, func(objectName string) error { 117 empty = false 118 objectPath := dirPath + objectName 119 if strings.HasSuffix(objectName, "/") { 120 dirQueue = append(dirQueue, objectPath) 121 return nil 122 } 123 return callback(objectPath) 124 }); err != nil { 125 return err 126 } 127 // Also report empty buckets 128 bucket, prefix := client.SplitBucketPrefix(dirPath) 129 if empty && len(prefix) == 0 { 130 if err := callback(bucket + "/"); err != nil { 131 return err 132 } 133 } 134 } 135 return nil 136 }