/
nds.go
156 lines (131 loc) · 3.87 KB
/
nds.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
package nds
import (
"bytes"
"crypto/sha1"
"encoding/binary"
"encoding/gob"
"encoding/hex"
"errors"
"math/rand"
"reflect"
"time"
"google.golang.org/appengine"
"google.golang.org/appengine/datastore"
"google.golang.org/appengine/memcache"
)
const (
// memcachePrefix is the namespace memcache uses to store entities.
memcachePrefix = "NDS1:"
// memcacheLockTime is the maximum length of time a memcache lock will be
// held for. 32 seconds is choosen as 30 seconds is the maximum amount of
// time an underlying datastore call will retry even if the API reports a
// success to the user.
memcacheLockTime = 32 * time.Second
// memcacheMaxKeySize is the maximum size a memcache item key can be. Keys
// greater than this size are automatically hashed to a smaller size.
memcacheMaxKeySize = 250
)
var (
typeOfPropertyLoadSaver = reflect.TypeOf(
(*datastore.PropertyLoadSaver)(nil)).Elem()
typeOfPropertyList = reflect.TypeOf(datastore.PropertyList(nil))
)
// The variables in this block are here so that we can test all error code
// paths by substituting the respective functions with error producing ones.
var (
datastoreDeleteMulti = datastore.DeleteMulti
datastoreGetMulti = datastore.GetMulti
datastorePutMulti = datastore.PutMulti
memcacheAddMulti = memcache.AddMulti
memcacheCompareAndSwapMulti = memcache.CompareAndSwapMulti
memcacheDeleteMulti = memcache.DeleteMulti
memcacheGetMulti = memcache.GetMulti
memcacheSetMulti = memcache.SetMulti
marshal = marshalPropertyList
unmarshal = unmarshalPropertyList
)
const (
noneItem uint32 = iota
entityItem
lockItem
)
func init() {
gob.Register(time.Time{})
gob.Register(datastore.ByteString{})
gob.Register(&datastore.Key{})
gob.Register(appengine.BlobKey(""))
gob.Register(appengine.GeoPoint{})
}
func itemLock() []byte {
b := make([]byte, 4)
binary.LittleEndian.PutUint32(b, rand.Uint32())
return b
}
func checkMultiArgs(keys []*datastore.Key, v reflect.Value) error {
if v.Kind() != reflect.Slice {
return errors.New("nds: vals is not a slice")
}
if len(keys) != v.Len() {
return errors.New("nds: keys and vals slices have different length")
}
isNilErr, nilErr := false, make(appengine.MultiError, len(keys))
for i, key := range keys {
if key == nil {
isNilErr = true
nilErr[i] = datastore.ErrInvalidKey
}
}
if isNilErr {
return nilErr
}
if v.Type() == typeOfPropertyList {
return errors.New("nds: PropertyList not supported")
}
elemType := v.Type().Elem()
if reflect.PtrTo(elemType).Implements(typeOfPropertyLoadSaver) {
return nil
}
switch elemType.Kind() {
case reflect.Struct, reflect.Interface:
return nil
case reflect.Ptr:
elemType = elemType.Elem()
if elemType.Kind() == reflect.Struct {
return nil
}
}
return errors.New("nds: unsupported vals type")
}
func createMemcacheKey(key *datastore.Key) string {
memcacheKey := memcachePrefix + key.Encode()
if len(memcacheKey) > memcacheMaxKeySize {
hash := sha1.Sum([]byte(memcacheKey))
memcacheKey = hex.EncodeToString(hash[:])
}
return memcacheKey
}
func marshalPropertyList(pl datastore.PropertyList) ([]byte, error) {
buf := bytes.Buffer{}
if err := gob.NewEncoder(&buf).Encode(&pl); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func unmarshalPropertyList(data []byte, pl *datastore.PropertyList) error {
return gob.NewDecoder(bytes.NewBuffer(data)).Decode(pl)
}
func setValue(val reflect.Value, pl datastore.PropertyList) error {
if reflect.PtrTo(val.Type()).Implements(typeOfPropertyLoadSaver) {
val = val.Addr()
}
if pls, ok := val.Interface().(datastore.PropertyLoadSaver); ok {
return pls.Load(pl)
}
if val.Kind() == reflect.Struct {
val = val.Addr()
}
if val.Kind() == reflect.Ptr && val.Type().Elem().Kind() == reflect.Struct && val.IsNil() {
val.Set(reflect.New(val.Type().Elem()))
}
return datastore.LoadStruct(val.Interface(), pl)
}