33package s3
44
55import (
6+ "context"
67 "path"
8+ "slices"
9+ "sort"
710 "strings"
811 "time"
912
1013 "github.com/itsHenry35/gofakes3"
1114 log "github.com/sirupsen/logrus"
1215)
1316
14- func (b * s3Backend ) entryListR (bucket , fdPath , name string , addPrefix bool , response * gofakes3.ObjectList ) error {
15- fp := path .Join (bucket , fdPath )
17+ // S3 ListObjects responses contain at most 1,000 keys.
18+ const maxListPageKeys int64 = 1000
19+
20+ type objectPage struct {
21+ result * gofakes3.ObjectList
22+ marker string
23+ maxKeys int64
24+ count int64
25+ lastKey string
26+ }
27+
28+ func newObjectPage (page gofakes3.ListBucketPage ) * objectPage {
29+ maxKeys := page .MaxKeys
30+ if maxKeys <= 0 || maxKeys > maxListPageKeys {
31+ maxKeys = maxListPageKeys
32+ }
33+ marker := ""
34+ if page .HasMarker {
35+ marker = page .Marker
36+ }
37+ return & objectPage {
38+ result : gofakes3 .NewObjectList (),
39+ marker : marker ,
40+ maxKeys : maxKeys ,
41+ }
42+ }
43+
44+ func (p * objectPage ) addContent (item * gofakes3.Content ) bool {
45+ if item .Key <= p .marker {
46+ return true
47+ }
48+ if p .count >= p .maxKeys {
49+ p .result .IsTruncated = true
50+ return false
51+ }
52+ p .result .Add (item )
53+ p .count ++
54+ p .lastKey = item .Key
55+ return true
56+ }
57+
58+ func (p * objectPage ) addPrefix (prefix string ) bool {
59+ if prefix <= p .marker {
60+ return true
61+ }
62+ if p .count >= p .maxKeys {
63+ p .result .IsTruncated = true
64+ return false
65+ }
66+ p .result .AddPrefix (prefix )
67+ p .count ++
68+ p .lastKey = prefix
69+ return true
70+ }
1671
17- dirEntries , err := getDirEntries (fp )
72+ func (p * objectPage ) finish () * gofakes3.ObjectList {
73+ if p .result .IsTruncated {
74+ p .result .NextMarker = p .lastKey
75+ }
76+ return p .result
77+ }
78+
79+ func (b * s3Backend ) listPage (
80+ ctx context.Context ,
81+ bucket , fdPath , name string ,
82+ addPrefix bool ,
83+ page gofakes3.ListBucketPage ,
84+ ) (* gofakes3.ObjectList , error ) {
85+ result := newObjectPage (page )
86+ _ , err := b .walkPage (ctx , bucket , fdPath , name , addPrefix , result )
87+ if err != nil {
88+ return nil , err
89+ }
90+ return result .finish (), nil
91+ }
92+
93+ // walkPage returns false after finding an entry beyond the requested page.
94+ func (b * s3Backend ) walkPage (
95+ ctx context.Context ,
96+ bucket , fdPath , name string ,
97+ addPrefix bool ,
98+ page * objectPage ,
99+ ) (bool , error ) {
100+ if err := ctx .Err (); err != nil {
101+ return false , err
102+ }
103+
104+ fp := path .Join (bucket , fdPath )
105+ dirEntries , err := b .listDir (ctx , fp )
18106 if err != nil {
19- return err
107+ return false , err
108+ }
109+ if err := ctx .Err (); err != nil {
110+ return false , err
20111 }
21112
22113 // workaround as s3 can't have empty files in directories, useful in deletions
23114 if len (dirEntries ) == 0 {
115+ if ! strings .HasPrefix (emptyObjectName , name ) {
116+ return true , nil
117+ }
24118 item := & gofakes3.Content {
25119 // Key: gofakes3.URLEncode(path.Join(fdPath, emptyObjectName)),
26120 Key : path .Join (fdPath , emptyObjectName ),
@@ -29,11 +123,15 @@ func (b *s3Backend) entryListR(bucket, fdPath, name string, addPrefix bool, resp
29123 Size : 0 ,
30124 StorageClass : gofakes3 .StorageStandard ,
31125 }
32- response .Add (item )
33126 log .Debugf ("Adding empty object %s to response" , item .Key )
34- return nil
127+ return page . addContent ( item ), nil
35128 }
36129
130+ dirEntries = slices .Clone (dirEntries )
131+ sort .Slice (dirEntries , func (i , j int ) bool {
132+ return dirEntries [i ].GetName () < dirEntries [j ].GetName ()
133+ })
134+
37135 for _ , entry := range dirEntries {
38136 object := entry .GetName ()
39137
@@ -47,12 +145,19 @@ func (b *s3Backend) entryListR(bucket, fdPath, name string, addPrefix bool, resp
47145 if entry .IsDir () {
48146 if addPrefix {
49147 // response.AddPrefix(gofakes3.URLEncode(objectPath))
50- response .AddPrefix (objectPath )
148+ if ! page .addPrefix (objectPath ) {
149+ return false , nil
150+ }
151+ continue
152+ }
153+ subtreePrefix := objectPath + "/"
154+ // A marker beyond this subtree lets us avoid an upstream directory read.
155+ if subtreePrefix <= page .marker && ! strings .HasPrefix (page .marker , subtreePrefix ) {
51156 continue
52157 }
53- err := b .entryListR ( bucket , path . Join ( fdPath , object ) , "" , false , response )
54- if err != nil {
55- return err
158+ keepGoing , err := b .walkPage ( ctx , bucket , objectPath , "" , false , page )
159+ if err != nil || ! keepGoing {
160+ return keepGoing , err
56161 }
57162 } else {
58163 item := & gofakes3.Content {
@@ -63,8 +168,10 @@ func (b *s3Backend) entryListR(bucket, fdPath, name string, addPrefix bool, resp
63168 Size : entry .GetSize (),
64169 StorageClass : gofakes3 .StorageStandard ,
65170 }
66- response .Add (item )
171+ if ! page .addContent (item ) {
172+ return false , nil
173+ }
67174 }
68175 }
69- return nil
176+ return true , nil
70177}
0 commit comments