-
Notifications
You must be signed in to change notification settings - Fork 0
/
datastore_appengine.go
216 lines (178 loc) · 4.95 KB
/
datastore_appengine.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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
// +build appengine
package dfs
import (
"os"
"sync"
"path/filepath"
"github.com/qedus/nds"
"golang.org/x/net/context"
"google.golang.org/appengine"
"google.golang.org/appengine/datastore"
)
type (
// clientImpl struct provides an instance with the same method signatures
// as the "cloud.google.com/go/datastore" package so more common datastore
// code can be reused. RunInTransaction is missing as it's different
clientImpl struct {
Get func(c context.Context, key *datastore.Key, val interface{}) error
GetMulti func(c context.Context, keys []*datastore.Key, vals interface{}) error
Put func(c context.Context, key *datastore.Key, val interface{}) (*datastore.Key, error)
PutMulti func(c context.Context, keys []*datastore.Key, vals interface{}) ([]*datastore.Key, error)
Delete func(c context.Context, key *datastore.Key) error
DeleteMulti func(c context.Context, keys []*datastore.Key) error
}
// FileSystem represents an appengine
// datastore backed filesystem session
FileSystem struct {
sync.RWMutex
ctx context.Context
client clientImpl
namespace string
kind string
data map[string]*FileData
}
clientType byte
)
var (
standard = clientImpl{
Get: datastore.Get,
GetMulti: datastore.GetMulti,
Put: datastore.Put,
PutMulti: datastore.PutMulti,
Delete: datastore.Delete,
DeleteMulti: datastore.DeleteMulti,
}
memcache = clientImpl{
Get: nds.Get,
GetMulti: nds.GetMulti,
Put: nds.Put,
PutMulti: nds.PutMulti,
Delete: nds.Delete,
DeleteMulti: nds.DeleteMulti,
}
)
const (
Standard clientType = iota
Memcache
)
// NewFileSystem creates a new appengine datastore backed filesystem
func NewFileSystem(ctx context.Context, namespace, kind string, clientType clientType) *FileSystem {
logger.Println("create appengine datastore filesystem", namespace)
if kind == "" {
kind = "file"
}
ctx, _ = appengine.Namespace(ctx, namespace)
var client clientImpl
switch clientType {
case Standard:
client = standard
case Memcache:
client = memcache
}
return &FileSystem{
ctx: ctx,
client: client,
namespace: namespace,
kind: kind,
data: make(map[string]*FileData),
}
}
func (fs *FileSystem) makeKey(name string) *datastore.Key {
return datastore.NewKey(fs.ctx, fs.kind, name, 0, nil)
}
func (fs *FileSystem) loadFileData(name string) (*FileData, error) {
key := fs.makeKey(name)
var fileData FileData
if err := fs.client.Get(fs.ctx, key, &fileData); err != nil {
if err == datastore.ErrNoSuchEntity {
return nil, ErrFileNotFound
}
return nil, &os.PathError{Op: "open", Path: name, Err: err}
}
fileData.name = name
return &fileData, nil
}
func (fs *FileSystem) saveFileData(fileData *FileData) error {
key := fs.makeKey(fileData.name)
_, err := fs.client.Put(fs.ctx, key, fileData)
return err
}
func (fs *FileSystem) saveFileDataMulti(files []*FileData) error {
keys := make([]*datastore.Key, len(files))
for i, file := range files {
keys[i] = fs.makeKey(file.name)
}
_, err := fs.client.PutMulti(fs.ctx, keys, files)
return err
}
func (fs *FileSystem) deleteFileData(name string) error {
key := fs.makeKey(name)
return fs.client.Delete(fs.ctx, key)
}
// TODO: use cursor for continuation rather than offset
// TODO: provide channel / callback func to populate?
func (fs *FileSystem) readDir(name string, offset, limit int) ([]os.FileInfo, error) {
files := []os.FileInfo{}
q := datastore.NewQuery(fs.kind)
q = q.Filter("parent =", name)
q = q.Order("__key__")
q = q.Offset(offset)
q = q.KeysOnly()
if limit > 0 {
q = q.Limit(limit)
}
it := q.Run(fs.ctx)
for {
var fileData FileData
k, err := it.Next(&fileData)
if err == datastore.Done {
break
}
if err != nil {
return nil, err
}
fileData.name = k.StringID()
files = append(files, NewFileInfo(&fileData))
}
return files, nil
}
func (fs *FileSystem) rename(oldname, newname string) error {
oldKey := fs.makeKey(oldname)
newKey := fs.makeKey(newname)
newParent := filepath.Dir(newname)
var fileData FileData
var result error
err := nds.RunInTransaction(fs.ctx, func(ctx context.Context) error {
if err := fs.client.Get(ctx, oldKey, &fileData); err != nil {
if err == datastore.ErrNoSuchEntity {
result = err
return nil
}
return err
}
fileData.name = newname
fileData.Parent = newParent
if _, err := fs.client.Put(ctx, newKey, &fileData); err != nil {
return err
}
return fs.client.Delete(ctx, oldKey)
}, &datastore.TransactionOptions{XG: true})
if result != nil {
return result
}
return err
}
func (fs *FileSystem) removeAllDescendents(path string) error {
q := datastore.NewQuery(fs.kind)
q = q.Filter("parent >=", path)
q = q.Filter("parent <", path+"\x7F")
q = q.KeysOnly()
keys, err := q.GetAll(fs.ctx, nil)
if err != nil {
return err
}
// add the parent
key := fs.makeKey(path)
keys = append(keys, key)
return fs.client.DeleteMulti(fs.ctx, keys)
}