-
Notifications
You must be signed in to change notification settings - Fork 28
/
storage.go
187 lines (156 loc) · 4.57 KB
/
storage.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
package joe
import (
"encoding/json"
"fmt"
"sort"
"sync"
"go.uber.org/zap"
)
// A Storage provides a convenient interface to a Memory implementation. It is
// responsible for how the actual key value data is encoded and provides
// concurrent access as well as logging.
//
// The default Storage that is returned by joe.NewStorage() encodes values as
// JSON and stores them in-memory.
type Storage struct {
logger *zap.Logger
mu sync.RWMutex
memory Memory
encoder MemoryEncoder
}
// The Memory interface allows the bot to persist data as key-value pairs.
// The default implementation of the Memory is to store all keys and values in
// a map (i.e. in-memory). Other implementations typically offer actual long term
// persistence into a file or to redis.
type Memory interface {
Set(key string, value []byte) error
Get(key string) ([]byte, bool, error)
Delete(key string) (bool, error)
Keys() ([]string, error)
Close() error
}
// A MemoryEncoder is used to encode and decode any values that are stored in
// the Memory. The default implementation that is used by the Storage uses a
// JSON encoding.
type MemoryEncoder interface {
Encode(value interface{}) ([]byte, error)
Decode(data []byte, target interface{}) error
}
type inMemory struct {
data map[string][]byte
}
type jsonEncoder struct{}
// NewStorage creates a new Storage instance that encodes values as JSON and
// stores them in-memory. You can change the memory and encoding via the
// provided setters.
func NewStorage(logger *zap.Logger) *Storage {
return &Storage{
logger: logger,
memory: newInMemory(),
encoder: new(jsonEncoder),
}
}
// SetMemory assigns a different Memory implementation.
func (s *Storage) SetMemory(m Memory) {
s.mu.Lock()
s.memory = m
s.mu.Unlock()
}
// SetMemoryEncoder assigns a different MemoryEncoder.
func (s *Storage) SetMemoryEncoder(enc MemoryEncoder) {
s.mu.Lock()
s.encoder = enc
s.mu.Unlock()
}
// Keys returns all keys known to the Memory.
func (s *Storage) Keys() ([]string, error) {
s.mu.RLock()
keys, err := s.memory.Keys()
s.mu.RUnlock()
sort.Strings(keys)
return keys, err
}
// Set encodes the given data and stores it in the Memory that is managed by the
// Storage.
func (s *Storage) Set(key string, value interface{}) error {
data, err := s.encoder.Encode(value)
if err != nil {
return fmt.Errorf("encode data: %w", err)
}
s.mu.Lock()
s.logger.Debug("Writing data to memory", zap.String("key", key))
err = s.memory.Set(key, data)
s.mu.Unlock()
return err
}
// Get retrieves the value under the requested key and decodes it into the
// passed "value" argument which must be a pointer. The boolean return value
// indicates if the value actually existed in the Memory and is false if it did
// not. It is legal to pass <nil> as the value if you only want to check if
// the given key exists but you do not actually care about the concrete value.
func (s *Storage) Get(key string, value interface{}) (bool, error) {
s.mu.RLock()
s.logger.Debug("Retrieving data from memory", zap.String("key", key))
data, ok, err := s.memory.Get(key)
s.mu.RUnlock()
if err != nil {
return false, err
}
if !ok || value == nil {
return ok, nil
}
err = s.encoder.Decode(data, value)
if err != nil {
return false, fmt.Errorf("decode data: %w", err)
}
return true, nil
}
// Delete removes a key and its associated value from the memory. The boolean
// return value indicates if the key existed or not.
func (s *Storage) Delete(key string) (bool, error) {
s.mu.Lock()
s.logger.Debug("Deleting data from memory", zap.String("key", key))
ok, err := s.memory.Delete(key)
s.mu.Unlock()
return ok, err
}
// Close closes the Memory that is managed by this Storage.
func (s *Storage) Close() error {
s.mu.Lock()
err := s.memory.Close()
s.mu.Unlock()
return err
}
func newInMemory() *inMemory {
return &inMemory{data: map[string][]byte{}}
}
func (m *inMemory) Set(key string, value []byte) error {
m.data[key] = value
return nil
}
func (m *inMemory) Get(key string) ([]byte, bool, error) {
value, ok := m.data[key]
return value, ok, nil
}
func (m *inMemory) Delete(key string) (bool, error) {
_, ok := m.data[key]
delete(m.data, key)
return ok, nil
}
func (m *inMemory) Keys() ([]string, error) {
keys := make([]string, 0, len(m.data))
for k := range m.data {
keys = append(keys, k)
}
return keys, nil
}
func (m *inMemory) Close() error {
m.data = map[string][]byte{}
return nil
}
func (jsonEncoder) Encode(value interface{}) ([]byte, error) {
return json.Marshal(value)
}
func (jsonEncoder) Decode(data []byte, target interface{}) error {
return json.Unmarshal(data, target)
}