add dispatcher

simplify transport implementation
support wildcard subscriptions
This commit is contained in:
Marcelo Pires
2018-09-24 16:13:57 +02:00
parent 8583e29645
commit 33a61d6d27
13 changed files with 786 additions and 410 deletions

40
internal/store/name.go Normal file
View File

@ -0,0 +1,40 @@
package store
import (
"strings"
)
type Name struct {
n string
patterns []string
}
func NewName(name string) *Name {
var n Name
n.n = name
//expand once
n.patterns = n.expand()
return &n
}
func (n *Name) Match(channel string) bool {
for i := range n.patterns {
if n.patterns[i] == channel {
return true
}
}
return false
}
func (n *Name) expand() []string {
segments := strings.Split(n.n, "/")
num_segments := len(segments)
patterns := make([]string, num_segments+1)
patterns[0] = "/**"
for i := 1; i < len(segments); i = i + 2 {
patterns[i] = strings.Join(segments[:i+1], "/") + "/**"
}
patterns[len(patterns)-2] = strings.Join(segments[:num_segments-1], "/") + "/*"
patterns[len(patterns)-1] = n.n
return patterns
}

View File

@ -0,0 +1,90 @@
package store
import (
"github.com/thesyncim/faye/subscription"
"sync"
)
type SubscriptionsStore struct {
mutex sync.Mutex
subs map[string][]*subscription.Subscription
//cache for expanded channel names
cache map[string]*Name
}
func NewStore(size int) *SubscriptionsStore {
return &SubscriptionsStore{
subs: make(map[string][]*subscription.Subscription, size),
cache: map[string]*Name{},
}
}
func (s *SubscriptionsStore) Add(sub *subscription.Subscription) {
s.mutex.Lock()
s.subs[sub.Name()] = append(s.subs[sub.Name()], sub)
s.mutex.Unlock()
}
//Match returns the subscriptions that match with the specified channel name
//Wildcard subscriptions are matched
func (s *SubscriptionsStore) Match(channel string) []*subscription.Subscription {
var (
matches []*subscription.Subscription
name *Name
ok bool
)
s.mutex.Lock()
if name, ok = s.cache[channel]; !ok {
name = NewName(channel)
s.cache[channel] = name
}
for _, subs := range s.subs {
for i := range subs {
if name.Match(subs[i].Name()) {
matches = append(matches, subs[i])
}
}
}
s.mutex.Unlock()
return matches
}
func (s *SubscriptionsStore) Remove(sub *subscription.Subscription) {
close(sub.MsgChannel())
s.mutex.Lock()
for channel, subs := range s.subs {
for i := range subs {
if subs[i] == sub {
//delete the subscription
subs = subs[:i+copy(subs[i:], subs[i+1:])]
if len(subs) == 0 {
delete(s.subs, channel)
}
goto end
}
}
}
end:
s.mutex.Unlock()
}
//RemoveAll removel all subscriptions and close all channels
func (s *SubscriptionsStore) RemoveAll() {
s.mutex.Lock()
for i := range s.subs {
//close all listeners
for j := range s.subs[i] {
s.subs[i][j].Unsubscribe()
close(s.subs[i][j].MsgChannel())
}
delete(s.subs, i)
}
s.mutex.Unlock()
}
//Count return the number of subscriptions associated with the specified channel
func (s *SubscriptionsStore) Count(channel string) int {
return len(s.Match(channel))
}

View File

@ -0,0 +1,140 @@
package store
import (
"github.com/thesyncim/faye/subscription"
"reflect"
"testing"
)
var (
wildcardSubscription, _ = subscription.NewSubscription("a", "/wildcard/*", nil, nil, nil)
simpleSubscription, _ = subscription.NewSubscription("b", "/foo/bar", nil, nil, nil)
)
func TestStore_Add(t *testing.T) {
type args struct {
name string
subs []*subscription.Subscription
}
tests := []struct {
name string
s *SubscriptionsStore
args args
expected *SubscriptionsStore
}{
{
name: "add one",
s: NewStore(10),
args: args{
name: "/wildcard/*",
subs: []*subscription.Subscription{wildcardSubscription},
},
expected: &SubscriptionsStore{
subs: map[string][]*subscription.Subscription{
"/wildcard/*": {wildcardSubscription},
},
},
},
{
name: "add three",
s: NewStore(10),
args: args{
name: "/wildcard/*",
subs: []*subscription.Subscription{wildcardSubscription, wildcardSubscription, wildcardSubscription},
},
expected: &SubscriptionsStore{
subs: map[string][]*subscription.Subscription{
"/wildcard/*": {wildcardSubscription, wildcardSubscription, wildcardSubscription},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
for i := range tt.args.subs {
tt.s.Add(tt.args.subs[i])
}
if !reflect.DeepEqual(tt.expected, tt.s) {
t.Fatalf("expecting :%v got: %v", tt.expected, tt.s)
}
})
}
}
func TestStore_Match(t *testing.T) {
type args struct {
name string
}
simpleStore := NewStore(0)
simpleStore.Add(simpleSubscription)
wildcardStore := NewStore(0)
wildcardStore.Add(wildcardSubscription)
tests := []struct {
name string
s *SubscriptionsStore
args args
want []*subscription.Subscription
}{
{
name: "match simple",
s: simpleStore,
want: []*subscription.Subscription{simpleSubscription},
args: args{
name: "/foo/bar",
},
},
{
name: "match wildcard 1",
s: wildcardStore,
want: []*subscription.Subscription{wildcardSubscription},
args: args{
name: "/wildcard/a",
},
},
{
name: "match wildcard 2",
s: wildcardStore,
want: []*subscription.Subscription{wildcardSubscription},
args: args{
name: "/wildcard/ccc",
},
},
{
name: "match non existent",
s: wildcardStore,
want: nil,
args: args{
name: "/wildcardsadasd",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.s.Match(tt.args.name); !reflect.DeepEqual(got, tt.want) {
t.Errorf("SubscriptionsStore.Match() = %#v, want %#v", got, tt.want)
}
})
}
}
func TestStore_Remove(t *testing.T) {
type args struct {
sub *subscription.Subscription
}
tests := []struct {
name string
s *SubscriptionsStore
args args
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.s.Remove(tt.args.sub)
})
}
}