-
-
Notifications
You must be signed in to change notification settings - Fork 79
/
gridfs.go
154 lines (136 loc) · 4.11 KB
/
gridfs.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package avatar
import (
"bytes"
"context"
"fmt"
"io"
"io/ioutil"
"time"
"github.com/pkg/errors"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/gridfs"
"go.mongodb.org/mongo-driver/mongo/options"
)
// NewGridFS makes gridfs (mongo) avatar store
func NewGridFS(client *mongo.Client, dbName, bucketName string, timeout time.Duration) *GridFS {
return &GridFS{client: client, db: client.Database(dbName), bucketName: bucketName, timeout: timeout}
}
// GridFS implements Store for GridFS
type GridFS struct {
client *mongo.Client
db *mongo.Database
bucketName string
timeout time.Duration
}
// Put avatar to gridfs object, try to resize
func (gf *GridFS) Put(userID string, reader io.Reader) (avatar string, err error) {
id := encodeID(userID)
bucket, err := gridfs.NewBucket(gf.db, &options.BucketOptions{Name: &gf.bucketName})
if err != nil {
return "", err
}
buf := &bytes.Buffer{}
if _, err = io.Copy(buf, reader); err != nil {
return "", errors.Wrapf(err, "can't read avatar for %s", userID)
}
avaHash := hash(buf.Bytes(), id)
_, err = bucket.UploadFromStream(id+imgSfx, buf, &options.UploadOptions{Metadata: bson.M{"hash": avaHash}})
return id + imgSfx, err
}
// Get avatar reader for avatar id.image
func (gf *GridFS) Get(avatar string) (reader io.ReadCloser, size int, err error) {
bucket, err := gridfs.NewBucket(gf.db, &options.BucketOptions{Name: &gf.bucketName})
if err != nil {
return nil, 0, err
}
buf := &bytes.Buffer{}
sz, e := bucket.DownloadToStreamByName(avatar, buf)
return ioutil.NopCloser(buf), int(sz), errors.Wrapf(e, "can't read avatar %s", avatar)
}
//
// ID returns a fingerprint of the avatar content. Uses MD5 because gridfs provides it directly
func (gf *GridFS) ID(avatar string) (id string) {
finfo := struct {
ID primitive.ObjectID `bson:"_id"`
Len int `bson:"length"`
FileName string `bson:"filename"`
MetaData struct {
Hash string `bson:"hash"`
} `bson:"metadata"`
}{}
bucket, err := gridfs.NewBucket(gf.db, &options.BucketOptions{Name: &gf.bucketName})
if err != nil {
return encodeID(avatar)
}
cursor, err := bucket.Find(bson.M{"filename": avatar})
if err != nil {
return encodeID(avatar)
}
ctx, cancel := context.WithTimeout(context.Background(), gf.timeout)
defer cancel()
if found := cursor.Next(ctx); found {
if err = cursor.Decode(&finfo); err != nil {
return encodeID(avatar)
}
return finfo.MetaData.Hash
}
return encodeID(avatar)
}
// Remove avatar from gridfs
func (gf *GridFS) Remove(avatar string) error {
bucket, err := gridfs.NewBucket(gf.db, &options.BucketOptions{Name: &gf.bucketName})
if err != nil {
return err
}
cursor, err := bucket.Find(bson.M{"filename": avatar})
if err != nil {
return err
}
r := struct {
ID primitive.ObjectID `bson:"_id"`
}{}
ctx, cancel := context.WithTimeout(context.Background(), gf.timeout)
defer cancel()
if found := cursor.Next(ctx); found {
if err := cursor.Decode(&r); err != nil {
return err
}
return bucket.Delete(r.ID)
}
return errors.Errorf("avatar %s not found", avatar)
}
// List all avatars (ids) on gfs
// note: id includes .image suffix
func (gf *GridFS) List() (ids []string, err error) {
bucket, err := gridfs.NewBucket(gf.db, &options.BucketOptions{Name: &gf.bucketName})
if err != nil {
return nil, err
}
gfsFile := struct {
Filename string `bson:"filename,omitempty"`
}{}
cursor, err := bucket.Find(bson.M{})
if err != nil {
return nil, err
}
ctx, cancel := context.WithTimeout(context.Background(), gf.timeout)
defer cancel()
for cursor.Next(ctx) {
if err := cursor.Decode(&gfsFile); err != nil {
return nil, err
}
ids = append(ids, gfsFile.Filename)
}
return ids, nil
}
// Close gridfs does nothing but satisfies interface
func (gf *GridFS) Close() error {
ctx, cancel := context.WithTimeout(context.Background(), gf.timeout)
defer cancel()
return gf.client.Disconnect(ctx)
}
func (gf *GridFS) String() string {
return fmt.Sprintf("mongo (grid fs), db=%s, bucket=%s", gf.db.Name(), gf.bucketName)
}