1package s3mem
2
3import (
4	"bytes"
5	"io"
6	"time"
7
8	"github.com/johannesboyne/gofakes3"
9	"github.com/johannesboyne/gofakes3/internal/s3io"
10	"github.com/ryszard/goskiplist/skiplist"
11)
12
13type versionGenFunc func() gofakes3.VersionID
14
15type versioningStatus int
16
17type bucket struct {
18	name         string
19	versioning   gofakes3.VersioningStatus
20	versionGen   versionGenFunc
21	creationDate gofakes3.ContentTime
22
23	objects *skiplist.SkipList
24}
25
26func newBucket(name string, at time.Time, versionGen versionGenFunc) *bucket {
27	return &bucket{
28		name:         name,
29		creationDate: gofakes3.NewContentTime(at),
30		versionGen:   versionGen,
31		objects:      skiplist.NewStringMap(),
32	}
33}
34
35type bucketObject struct {
36	name     string
37	data     *bucketData
38	versions *skiplist.SkipList
39}
40
41func (b *bucketObject) Iterator() *bucketObjectIterator {
42	var iter skiplist.Iterator
43	if b.versions != nil {
44		iter = b.versions.Iterator()
45	}
46
47	return &bucketObjectIterator{
48		data: b.data,
49		iter: iter,
50	}
51}
52
53type bucketObjectIterator struct {
54	data     *bucketData
55	iter     skiplist.Iterator
56	cur      *bucketData
57	seenData bool
58	done     bool
59}
60
61func (b *bucketObjectIterator) Seek(key gofakes3.VersionID) bool {
62	if b.iter.Seek(key) {
63		return true
64	}
65
66	b.iter = nil
67	if b.data != nil && b.data.versionID == key {
68		return true
69	}
70
71	b.data = nil
72	b.done = true
73
74	return false
75}
76
77func (b *bucketObjectIterator) Next() bool {
78	if b.done {
79		return false
80	}
81
82	if b.iter != nil {
83		iterAlive := b.iter.Next()
84		if iterAlive {
85			b.cur = b.iter.Value().(*bucketData)
86			return true
87		}
88
89		b.iter.Close()
90		b.iter = nil
91	}
92
93	if b.data != nil {
94		b.cur = b.data
95		b.data = nil
96		return true
97	}
98
99	b.done = true
100	return false
101}
102
103func (b *bucketObjectIterator) Close() {
104	if b.iter != nil {
105		b.iter.Close()
106	}
107	b.done = true
108}
109
110func (b *bucketObjectIterator) Value() *bucketData {
111	return b.cur
112}
113
114type bucketData struct {
115	name         string
116	lastModified time.Time
117	versionID    gofakes3.VersionID
118	deleteMarker bool
119	body         []byte
120	hash         []byte
121	etag         string
122	metadata     map[string]string
123}
124
125func (bi *bucketData) toObject(rangeRequest *gofakes3.ObjectRangeRequest, withBody bool) (obj *gofakes3.Object, err error) {
126	sz := int64(len(bi.body))
127	data := bi.body
128
129	var contents io.ReadCloser
130	var rnge *gofakes3.ObjectRange
131
132	if withBody {
133		// In case of a range request the correct part of the slice is extracted:
134		rnge, err = rangeRequest.Range(sz)
135		if err != nil {
136			return nil, err
137		}
138
139		if rnge != nil {
140			data = data[rnge.Start : rnge.Start+rnge.Length]
141		}
142
143		// The data slice should be completely replaced if the bucket item is edited, so
144		// it should be safe to return the data slice directly.
145		contents = s3io.ReaderWithDummyCloser{bytes.NewReader(data)}
146
147	} else {
148		contents = s3io.NoOpReadCloser{}
149	}
150
151	return &gofakes3.Object{
152		Name:           bi.name,
153		Hash:           bi.hash,
154		Metadata:       bi.metadata,
155		Size:           sz,
156		Range:          rnge,
157		IsDeleteMarker: bi.deleteMarker,
158		VersionID:      bi.versionID,
159		Contents:       contents,
160	}, nil
161}
162
163func (b *bucket) setVersioning(enabled bool) {
164	if enabled {
165		b.versioning = gofakes3.VersioningEnabled
166	} else if b.versioning == gofakes3.VersioningEnabled {
167		b.versioning = gofakes3.VersioningSuspended
168	}
169}
170
171func (b *bucket) object(objectName string) (obj *bucketObject) {
172	objIface, _ := b.objects.Get(objectName)
173	if objIface == nil {
174		return nil
175	}
176	obj, _ = objIface.(*bucketObject)
177	return obj
178}
179
180func (b *bucket) objectVersion(objectName string, versionID gofakes3.VersionID) (*bucketData, error) {
181	obj := b.object(objectName)
182	if obj == nil {
183		return nil, gofakes3.KeyNotFound(objectName)
184	}
185
186	if obj.data != nil && obj.data.versionID == versionID {
187		return obj.data, nil
188	}
189	if obj.versions == nil {
190		return nil, gofakes3.ErrNoSuchVersion
191	}
192	versionIface, _ := obj.versions.Get(versionID)
193	if versionIface == nil {
194		return nil, gofakes3.ErrNoSuchVersion
195	}
196
197	return versionIface.(*bucketData), nil
198}
199
200func (b *bucket) put(name string, item *bucketData) {
201	// Always generate a version for convenience; we can just mask it on return.
202	item.versionID = b.versionGen()
203
204	object := b.object(name)
205	if object == nil {
206		object = &bucketObject{name: name}
207		b.objects.Set(name, object)
208	}
209
210	if b.versioning == gofakes3.VersioningEnabled {
211		if object.data != nil {
212			if object.versions == nil {
213				object.versions = skiplist.NewCustomMap(func(l, r interface{}) bool {
214					return l.(gofakes3.VersionID) < r.(gofakes3.VersionID)
215				})
216			}
217			object.versions.Set(object.data.versionID, object.data)
218		}
219	}
220
221	object.data = item
222}
223
224func (b *bucket) rm(name string, at time.Time) (result gofakes3.ObjectDeleteResult, rerr error) {
225	object := b.object(name)
226	if object == nil {
227		// S3 does not report an error when attemping to delete a key that does not exist
228		return result, nil
229	}
230
231	if b.versioning == gofakes3.VersioningEnabled {
232		item := &bucketData{lastModified: at, name: name, deleteMarker: true}
233		b.put(name, item)
234		result.IsDeleteMarker = true
235		result.VersionID = item.versionID
236
237	} else {
238		object.data = nil
239		if object.versions == nil || object.versions.Len() == 0 {
240			b.objects.Delete(name)
241		}
242	}
243
244	return result, nil
245}
246
247func (b *bucket) rmVersion(name string, versionID gofakes3.VersionID, at time.Time) (result gofakes3.ObjectDeleteResult, rerr error) {
248	object := b.object(name)
249	if object == nil {
250		return result, nil
251
252	} else if object.data != nil && object.data.versionID == versionID {
253		result.VersionID = versionID
254		result.IsDeleteMarker = object.data.deleteMarker
255		object.data = nil
256
257	} else if object.versions != nil {
258		versionIface, ok := object.versions.Delete(versionID)
259		if !ok {
260			// S3 does not report an error when attemping to delete a key that does not exist
261			return result, nil
262		}
263
264		version := versionIface.(*bucketData)
265		result.VersionID = version.versionID
266		result.IsDeleteMarker = version.deleteMarker
267	}
268
269	if object.data == nil && (object.versions == nil || object.versions.Len() == 0) {
270		b.objects.Delete(name)
271	}
272
273	return result, nil
274}
275