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
|
|
|
|
|
|
|
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-22 09:23:14 +00:00
|
|
|
var ErrNotObject = errors.New("Scoped value not an object")
|
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
|
|
|
|
|
|
|
// Empty string if doesn't exist, ErrNotObject if self or parent not an object
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
}
|