go-gun/gun/scoped.go

93 lines
2.8 KiB
Go
Raw Permalink 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-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
}