155 lines
3.0 KiB
Go
155 lines
3.0 KiB
Go
package s3
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/md5"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.kapelle.org/niklas/s3browser/internal/types"
|
|
)
|
|
|
|
type mockS3 struct {
|
|
buckets []string
|
|
objects map[types.ID]mockObject
|
|
}
|
|
|
|
type mockObject struct {
|
|
content []byte
|
|
contentType string
|
|
lastMod time.Time
|
|
}
|
|
|
|
type mockObjectReader struct {
|
|
*bytes.Reader
|
|
}
|
|
|
|
func (r mockObjectReader) Close() error {
|
|
// NOOP
|
|
return nil
|
|
}
|
|
|
|
func NewMockS3(buckets []string) (S3Service, error) {
|
|
return &mockS3{
|
|
buckets: buckets,
|
|
objects: map[types.ID]mockObject{},
|
|
}, nil
|
|
}
|
|
|
|
func (m *mockS3) ListBuckets(ctx context.Context) ([]string, error) {
|
|
return m.buckets, nil
|
|
}
|
|
|
|
func (m *mockS3) ListObjects(ctx context.Context, id types.ID) ([]Object, error) {
|
|
var results []Object
|
|
|
|
dirs := make(map[string]bool)
|
|
|
|
depth := len(strings.Split(id.Key, "/"))
|
|
|
|
for k, v := range m.objects {
|
|
if k.Bucket == id.Bucket {
|
|
if k.Parent().Key == id.Key {
|
|
results = append(results, *mockObjToObject(v, k))
|
|
} else if strings.HasPrefix(k.Key, id.Key) {
|
|
s := strings.Join(strings.Split(k.Key, "/")[:depth], "/") + "/"
|
|
dirs[s] = true
|
|
}
|
|
}
|
|
}
|
|
|
|
for k := range dirs {
|
|
results = append(results, Object{
|
|
ID: types.ID{
|
|
Bucket: id.Bucket,
|
|
Key: k,
|
|
},
|
|
})
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
func (m *mockS3) ListObjectsRecursive(ctx context.Context, id types.ID) ([]Object, error) {
|
|
var results []Object
|
|
|
|
for k, v := range m.objects {
|
|
if k.Bucket == id.Bucket {
|
|
if strings.HasPrefix(k.Key, id.Key) {
|
|
results = append(results, *mockObjToObject(v, k))
|
|
}
|
|
}
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
func (m *mockS3) GetObject(ctx context.Context, id types.ID) (ObjectReader, error) {
|
|
mockObj, exist := m.objects[id]
|
|
|
|
if !exist {
|
|
return nil, fmt.Errorf("Object not found")
|
|
}
|
|
|
|
reader := bytes.NewReader(mockObj.content)
|
|
|
|
return mockObjectReader{reader}, nil
|
|
}
|
|
|
|
func (m *mockS3) PutObject(ctx context.Context, id types.ID, reader io.Reader, objectSize int64) error {
|
|
content, err := ioutil.ReadAll(reader)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
m.objects[id] = mockObject{
|
|
content: content,
|
|
lastMod: time.Now(),
|
|
contentType: "application/octet-stream", // TODO: detect MIME type or dont its just a mock after all
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *mockS3) CopyObject(ctx context.Context, src types.ID, dest types.ID) error {
|
|
srcObj, exist := m.objects[src]
|
|
|
|
if !exist {
|
|
return fmt.Errorf("Object not found")
|
|
}
|
|
|
|
m.objects[dest] = srcObj
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *mockS3) StatObject(ctx context.Context, id types.ID) (*Object, error) {
|
|
mockObj, exist := m.objects[id]
|
|
|
|
if !exist {
|
|
return nil, fmt.Errorf("Object not found")
|
|
}
|
|
|
|
return mockObjToObject(mockObj, id), nil
|
|
}
|
|
|
|
func (m *mockS3) RemoveObject(ctx context.Context, id types.ID) error {
|
|
delete(m.objects, id)
|
|
return nil
|
|
}
|
|
|
|
func mockObjToObject(mockObj mockObject, id types.ID) *Object {
|
|
return &Object{
|
|
ID: id,
|
|
Size: int64(len(mockObj.content)),
|
|
ContentType: mockObj.contentType,
|
|
LastModified: mockObj.lastMod,
|
|
ETag: fmt.Sprintf("%x", md5.Sum(mockObj.content)),
|
|
}
|
|
}
|