go-gun/gun/scoped.go

226 lines
5.8 KiB
Go
Raw Normal View History

2019-02-20 20:54:46 +00:00
package gun
2019-02-22 06:46:19 +00:00
import (
"context"
2019-02-22 09:23:14 +00:00
"errors"
2019-02-22 06:46:19 +00:00
"sync"
)
2019-02-20 20:54:46 +00:00
type Scoped struct {
2019-02-22 09:23:14 +00:00
gun *Gun
parent *Scoped
field string
cachedParentSoul string
cachedParentSoulLock sync.RWMutex
2019-02-22 06:46:19 +00:00
2019-02-22 21:40:55 +00:00
resultChansToListeners map[<-chan *Result]*messageIDListener
resultChansToListenersLock sync.Mutex
2019-02-20 20:54:46 +00:00
}
2019-02-22 06:46:19 +00:00
type messageIDListener struct {
id string
2019-02-22 21:40:55 +00:00
results chan *Result
2019-02-22 09:23:14 +00:00
receivedMessages chan *MessageReceived
2019-02-20 20:54:46 +00:00
}
2019-02-22 09:23:14 +00:00
func newScoped(gun *Gun, parent *Scoped, field string) *Scoped {
2019-02-22 06:46:19 +00:00
return &Scoped{
2019-02-22 21:40:55 +00:00
gun: gun,
parent: parent,
field: field,
resultChansToListeners: map[<-chan *Result]*messageIDListener{},
2019-02-22 06:46:19 +00:00
}
}
2019-02-22 21:40:55 +00:00
type Result struct {
2019-02-22 06:46:19 +00:00
// This can be a context error on cancelation
Err error
Field string
2019-02-22 21:40:55 +00:00
// Nil if the value doesn't exist, exists and is nil, or there's an error
Value Value
State int64 // This can be 0 for errors or top-level value relations
ValueExists bool
2019-02-22 06:46:19 +00:00
// Nil when local and sometimes on error
2019-02-22 18:40:02 +00:00
peer *gunPeer
2019-02-20 20:54:46 +00:00
}
2019-02-22 09:23:14 +00:00
var ErrNotObject = errors.New("Scoped value not an object")
var ErrLookupOnTopLevel = errors.New("Cannot do lookup on top level")
// Empty string if doesn't exist, ErrNotObject if self or parent not an object
func (s *Scoped) Soul(ctx context.Context) (string, error) {
s.cachedParentSoulLock.RLock()
cachedParentSoul := s.cachedParentSoul
s.cachedParentSoulLock.RUnlock()
if cachedParentSoul != "" {
return cachedParentSoul, nil
2019-02-22 21:40:55 +00:00
} else if r := s.Val(ctx); r.Err != nil {
return "", r.Err
} else if !r.ValueExists {
2019-02-22 09:23:14 +00:00
return "", nil
2019-02-22 21:40:55 +00:00
} else if rel, ok := r.Value.(ValueRelation); !ok {
2019-02-22 09:23:14 +00:00
return "", ErrNotObject
} else {
s.cachedParentSoulLock.Lock()
s.cachedParentSoul = string(rel)
s.cachedParentSoulLock.Unlock()
return string(rel), nil
}
2019-02-20 20:54:46 +00:00
}
2019-02-22 06:46:19 +00:00
2019-02-22 21:40:55 +00:00
func (s *Scoped) Put(ctx context.Context, val Value) <-chan *Result {
panic("TODO")
}
func (s *Scoped) Val(ctx context.Context) *Result {
2019-02-22 06:46:19 +00:00
// Try local before remote
2019-02-22 21:40:55 +00:00
if r := s.ValLocal(ctx); r.Err != nil || r.ValueExists {
return r
2019-02-22 06:46:19 +00:00
}
return s.ValRemote(ctx)
}
2019-02-22 21:40:55 +00:00
func (s *Scoped) ValLocal(ctx context.Context) *Result {
2019-02-22 09:23:14 +00:00
// If there is no parent, this is just the relation
if s.parent == nil {
2019-02-22 21:40:55 +00:00
return &Result{Field: s.field, Value: ValueRelation(s.field), ValueExists: true}
2019-02-22 06:46:19 +00:00
}
2019-02-22 21:40:55 +00:00
r := &Result{Field: s.field}
2019-02-22 09:23:14 +00:00
// Need parent soul for lookup
var parentSoul string
2019-02-22 21:40:55 +00:00
if parentSoul, r.Err = s.parent.Soul(ctx); r.Err == nil {
var vs *ValueWithState
if vs, r.Err = s.gun.storage.Get(ctx, parentSoul, s.field); r.Err == ErrStorageNotFound {
r.Err = nil
} else if r.Err == nil {
r.Value, r.State, r.ValueExists = vs.Value, vs.State, true
2019-02-22 09:23:14 +00:00
}
}
2019-02-22 21:40:55 +00:00
return r
2019-02-22 06:46:19 +00:00
}
2019-02-22 21:40:55 +00:00
func (s *Scoped) ValRemote(ctx context.Context) *Result {
2019-02-22 09:23:14 +00:00
if s.parent == nil {
2019-02-22 21:40:55 +00:00
return &Result{Err: ErrLookupOnTopLevel, Field: s.field}
2019-02-22 09:23:14 +00:00
}
2019-02-22 06:46:19 +00:00
ch := s.OnRemote(ctx)
defer s.Off(ch)
return <-ch
}
2019-02-22 21:40:55 +00:00
func (s *Scoped) On(ctx context.Context) <-chan *Result {
ch := make(chan *Result, 1)
2019-02-22 09:23:14 +00:00
if s.parent == nil {
2019-02-22 21:40:55 +00:00
ch <- &Result{Err: ErrLookupOnTopLevel, Field: s.field}
close(ch)
2019-02-22 09:23:14 +00:00
} else {
2019-02-22 21:40:55 +00:00
if r := s.ValLocal(ctx); r.Err != nil || r.ValueExists {
ch <- r
2019-02-22 09:23:14 +00:00
}
go s.onRemote(ctx, ch)
2019-02-22 06:46:19 +00:00
}
return ch
}
2019-02-22 21:40:55 +00:00
func (s *Scoped) OnRemote(ctx context.Context) <-chan *Result {
ch := make(chan *Result, 1)
2019-02-22 09:23:14 +00:00
if s.parent == nil {
2019-02-22 21:40:55 +00:00
ch <- &Result{Err: ErrLookupOnTopLevel, Field: s.field}
close(ch)
2019-02-22 09:23:14 +00:00
} else {
go s.onRemote(ctx, ch)
}
2019-02-22 06:46:19 +00:00
return ch
}
2019-02-22 21:40:55 +00:00
func (s *Scoped) onRemote(ctx context.Context, ch chan *Result) {
2019-02-22 09:23:14 +00:00
if s.parent == nil {
panic("No parent")
}
// We have to get the parent soul first
parentSoul, err := s.parent.Soul(ctx)
if err != nil {
2019-02-22 21:40:55 +00:00
ch <- &Result{Err: ErrLookupOnTopLevel, Field: s.field}
2019-02-22 09:23:14 +00:00
return
}
2019-02-22 06:46:19 +00:00
// Create get request
req := &Message{
ID: randString(9),
2019-02-22 09:23:14 +00:00
Get: &MessageGetRequest{Soul: parentSoul, Field: s.field},
2019-02-22 06:46:19 +00:00
}
// Make a chan to listen for received messages and link it to
// the given one so we can turn it "off". Off will close this
// chan.
2019-02-22 09:23:14 +00:00
msgCh := make(chan *MessageReceived)
2019-02-22 21:40:55 +00:00
s.resultChansToListenersLock.Lock()
s.resultChansToListeners[ch] = &messageIDListener{req.ID, ch, msgCh}
s.resultChansToListenersLock.Unlock()
2019-02-22 06:46:19 +00:00
// Listen for responses to this get
s.gun.RegisterMessageIDPutListener(req.ID, msgCh)
// TODO: only for children: s.gun.RegisterValueIDPutListener(s.id, msgCh)
// Handle received messages turning them to value fetches
go func() {
for {
select {
case <-ctx.Done():
2019-02-22 21:40:55 +00:00
ch <- &Result{Err: ctx.Err(), Field: s.field}
2019-02-22 06:46:19 +00:00
s.Off(ch)
return
case msg, ok := <-msgCh:
if !ok {
return
}
2019-02-22 21:40:55 +00:00
r := &Result{Field: s.field, peer: msg.peer}
2019-02-22 09:23:14 +00:00
// We asked for a single field, should only get that field or it doesn't exist
if n := msg.Put[parentSoul]; n != nil && n.Values[s.field] != nil {
2019-02-22 21:40:55 +00:00
r.Value, r.State, r.ValueExists = n.Values[s.field], n.State[s.field], true
2019-02-22 06:46:19 +00:00
}
2019-02-22 09:23:14 +00:00
// TODO: conflict resolution and defer
// TODO: dedupe
// TODO: store and cache
2019-02-22 21:40:55 +00:00
safeResultSend(ch, r)
2019-02-22 06:46:19 +00:00
}
}
}()
// Send async, sending back errors
go func() {
for peerErr := range s.gun.Send(ctx, req) {
2019-02-22 21:40:55 +00:00
safeResultSend(ch, &Result{
2019-02-22 06:46:19 +00:00
Err: peerErr.Err,
Field: s.field,
2019-02-22 18:40:02 +00:00
peer: peerErr.peer,
2019-02-22 06:46:19 +00:00
})
}
}()
}
2019-02-22 21:40:55 +00:00
func (s *Scoped) Off(ch <-chan *Result) bool {
s.resultChansToListenersLock.Lock()
l := s.resultChansToListeners[ch]
delete(s.resultChansToListeners, ch)
s.resultChansToListenersLock.Unlock()
2019-02-22 06:46:19 +00:00
if l != nil {
// Unregister the chan
s.gun.UnregisterMessageIDPutListener(l.id)
2019-02-22 21:40:55 +00:00
// Close the message chan and the result chan
2019-02-22 06:46:19 +00:00
close(l.receivedMessages)
2019-02-22 21:40:55 +00:00
close(l.results)
2019-02-22 06:46:19 +00:00
}
return l != nil
}
func (s *Scoped) Scoped(ctx context.Context, key string, children ...string) *Scoped {
2019-02-22 09:23:14 +00:00
ret := newScoped(s.gun, s, key)
for _, child := range children {
ret = newScoped(s.gun, ret, child)
}
return ret
2019-02-22 06:46:19 +00:00
}
2019-02-22 21:40:55 +00:00
func safeResultSend(ch chan<- *Result, r *Result) {
2019-02-22 06:46:19 +00:00
// Due to the fact that we may send on a closed channel here, we ignore the panic
defer func() { recover() }()
2019-02-22 21:40:55 +00:00
ch <- r
2019-02-22 06:46:19 +00:00
}