154 lines
3.2 KiB
Go
154 lines
3.2 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
)
|
|
|
|
// memBucket is a state in memory, with the bus's revisions and its refusal of a lost race.
|
|
type memBucket struct {
|
|
mu sync.Mutex
|
|
values map[string][]byte
|
|
revs map[string]uint64
|
|
rev uint64
|
|
writes []string // "op key", in order
|
|
fail map[string]error
|
|
}
|
|
|
|
func newBucket() *memBucket {
|
|
return &memBucket{values: map[string][]byte{}, revs: map[string]uint64{}, fail: map[string]error{}}
|
|
}
|
|
|
|
func (b *memBucket) Get(key string) (*stdio.StateEntry, error) {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
if err := b.fail["get "+key]; err != nil {
|
|
return nil, err
|
|
}
|
|
v, ok := b.values[key]
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
return &stdio.StateEntry{Key: key, Value: json.RawMessage(v), Revision: b.revs[key]}, nil
|
|
}
|
|
|
|
func (b *memBucket) write(op, key string, value any) (uint64, error) {
|
|
if err := b.fail[op+" "+key]; err != nil {
|
|
return 0, err
|
|
}
|
|
data, err := json.Marshal(value)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
b.rev++
|
|
b.values[key], b.revs[key] = data, b.rev
|
|
b.writes = append(b.writes, op+" "+key)
|
|
return b.rev, nil
|
|
}
|
|
|
|
func (b *memBucket) Create(key string, value any) (uint64, error) {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
if _, ok := b.values[key]; ok {
|
|
return 0, fmt.Errorf("%s: %w", key, stdio.ErrChanged)
|
|
}
|
|
return b.write("create", key, value)
|
|
}
|
|
|
|
func (b *memBucket) Update(key string, value any, revision uint64) (uint64, error) {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
if b.revs[key] != revision {
|
|
return 0, fmt.Errorf("%s: %w", key, stdio.ErrChanged)
|
|
}
|
|
return b.write("update", key, value)
|
|
}
|
|
|
|
func (b *memBucket) Put(key string, value any) (uint64, error) {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
return b.write("put", key, value)
|
|
}
|
|
|
|
func (b *memBucket) Delete(key string) error {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
if err := b.fail["delete "+key]; err != nil {
|
|
return err
|
|
}
|
|
delete(b.values, key)
|
|
delete(b.revs, key)
|
|
b.writes = append(b.writes, "delete "+key)
|
|
return nil
|
|
}
|
|
|
|
func (b *memBucket) Keys() ([]string, error) {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
keys := []string{}
|
|
for k := range b.values {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
return keys, nil
|
|
}
|
|
|
|
// value reads a key into its shape; a missing key is the zero value and false.
|
|
func (b *memBucket) value(key string, v any) bool {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
raw, ok := b.values[key]
|
|
if ok {
|
|
if err := json.Unmarshal(raw, v); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
return ok
|
|
}
|
|
|
|
// writesTo are the writes to keys starting with prefix.
|
|
func (b *memBucket) writesTo(prefix string) []string {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
var out []string
|
|
for _, w := range b.writes {
|
|
if _, key, _ := strings.Cut(w, " "); strings.HasPrefix(key, prefix) {
|
|
out = append(out, w)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// sentEvent is one event a fake sink was handed.
|
|
type sentEvent struct {
|
|
Name string
|
|
Body any
|
|
}
|
|
|
|
type eventLog struct {
|
|
mu sync.Mutex
|
|
sent []sentEvent
|
|
}
|
|
|
|
func (e *eventLog) Emit(name string, body any, _ ...stdio.EmitOptions) error {
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
e.sent = append(e.sent, sentEvent{name, body})
|
|
return nil
|
|
}
|
|
|
|
func (e *eventLog) names() []string {
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
var out []string
|
|
for _, s := range e.sent {
|
|
out = append(out, s.Name)
|
|
}
|
|
return out
|
|
}
|