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-25 04:23:15 +00:00
|
|
|
"fmt"
|
2019-02-22 06:46:19 +00:00
|
|
|
"sync"
|
|
|
|
)
|
2019-02-20 20:54:46 +00:00
|
|
|
|
2019-02-26 21:59:44 +00:00
|
|
|
// Scoped is a contextual, namespaced field to read or write.
|
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-25 04:23:15 +00:00
|
|
|
fetchResultListeners map[<-chan *FetchResult]*fetchResultListener
|
|
|
|
fetchResultListenersLock sync.Mutex
|
2019-02-20 20:54:46 +00:00
|
|
|
|
2019-02-25 04:23:15 +00:00
|
|
|
putResultListeners map[<-chan *PutResult]*putResultListener
|
|
|
|
putResultListenersLock sync.Mutex
|
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-25 04:23:15 +00:00
|
|
|
gun: gun,
|
|
|
|
parent: parent,
|
|
|
|
field: field,
|
|
|
|
fetchResultListeners: map[<-chan *FetchResult]*fetchResultListener{},
|
|
|
|
putResultListeners: map[<-chan *PutResult]*putResultListener{},
|
2019-02-22 06:46:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-26 21:59:44 +00:00
|
|
|
// ErrNotObject occurs when a put or a fetch is attempted as a child of an
|
|
|
|
// existing, non-relation value.
|
2019-02-22 09:23:14 +00:00
|
|
|
var ErrNotObject = errors.New("Scoped value not an object")
|
2019-02-26 21:59:44 +00:00
|
|
|
|
|
|
|
// ErrLookupOnTopLevel occurs when a put or remote fetch is attempted on
|
|
|
|
// a top-level field.
|
2019-02-25 04:23:15 +00:00
|
|
|
var ErrLookupOnTopLevel = errors.New("Cannot do put/lookup on top level")
|
2019-02-22 09:23:14 +00:00
|
|
|
|
2019-02-26 21:59:44 +00:00
|
|
|
// Soul returns the current soul of this value relation. It returns a cached
|
|
|
|
// value if called before. Otherwise, it does a FetchOne to get the value
|
|
|
|
// and return its soul if a relation. If any parent is not a relation or this
|
|
|
|
// value exists and is not a relation, ErrNotObject is returned. If this field
|
|
|
|
// doesn't exist yet, an empty string is returned with no error. Otherwise,
|
|
|
|
// the soul of the relation is returned. The context can be used to timeout the
|
|
|
|
// fetch.
|
2019-02-22 09:23:14 +00:00
|
|
|
func (s *Scoped) Soul(ctx context.Context) (string, error) {
|
2019-02-25 04:23:15 +00:00
|
|
|
if cachedSoul := s.cachedSoul(); cachedSoul != "" {
|
|
|
|
return cachedSoul, nil
|
|
|
|
} else if r := s.FetchOne(ctx); r.Err != nil {
|
2019-02-22 21:40:55 +00:00
|
|
|
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
|
2019-02-25 04:23:15 +00:00
|
|
|
} else if !s.setCachedSoul(rel) {
|
|
|
|
return "", fmt.Errorf("Concurrent soul cache set")
|
2019-02-22 09:23:14 +00:00
|
|
|
} else {
|
|
|
|
return string(rel), nil
|
|
|
|
}
|
2019-02-20 20:54:46 +00:00
|
|
|
}
|
2019-02-22 06:46:19 +00:00
|
|
|
|
2019-02-25 04:23:15 +00:00
|
|
|
func (s *Scoped) cachedSoul() string {
|
|
|
|
s.cachedParentSoulLock.RLock()
|
|
|
|
defer s.cachedParentSoulLock.RUnlock()
|
|
|
|
return s.cachedParentSoul
|
2019-02-22 06:46:19 +00:00
|
|
|
}
|
|
|
|
|
2019-02-25 04:23:15 +00:00
|
|
|
func (s *Scoped) setCachedSoul(val ValueRelation) bool {
|
|
|
|
s.cachedParentSoulLock.Lock()
|
|
|
|
defer s.cachedParentSoulLock.Unlock()
|
|
|
|
if s.cachedParentSoul != "" {
|
|
|
|
return false
|
2019-02-22 06:46:19 +00:00
|
|
|
}
|
2019-02-25 04:23:15 +00:00
|
|
|
s.cachedParentSoul = string(val)
|
|
|
|
return true
|
2019-02-22 06:46:19 +00:00
|
|
|
}
|
|
|
|
|
2019-02-26 21:59:44 +00:00
|
|
|
// Scoped returns a scoped instance to the given field and children for reading
|
|
|
|
// and writing. This is the equivalent of calling the Gun JS API "get" function
|
|
|
|
// (sans callback) for each field/child.
|
|
|
|
func (s *Scoped) Scoped(ctx context.Context, field string, children ...string) *Scoped {
|
|
|
|
ret := newScoped(s.gun, s, field)
|
2019-02-22 09:23:14 +00:00
|
|
|
for _, child := range children {
|
|
|
|
ret = newScoped(s.gun, ret, child)
|
|
|
|
}
|
|
|
|
return ret
|
2019-02-22 06:46:19 +00:00
|
|
|
}
|