Skip to content

Commit

Permalink
always invoke (Add|Remove)MatchSignal on bus object, add additional f…
Browse files Browse the repository at this point in the history
…ilter options
  • Loading branch information
mechmind authored and jsouthworth committed Oct 25, 2018
1 parent 22e5df4 commit 66d97ae
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 10 deletions.
64 changes: 54 additions & 10 deletions object.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ type BusObject interface {
CallWithContext(ctx context.Context, method string, flags Flags, args ...interface{}) *Call
Go(method string, flags Flags, ch chan *Call, args ...interface{}) *Call
GoWithContext(ctx context.Context, method string, flags Flags, ch chan *Call, args ...interface{}) *Call
AddMatchSignal(iface, member string, options ...MatchOption) *Call
RemoveMatchSignal(iface, member string, options ...MatchOption) *Call
GetProperty(p string) (Variant, error)
Destination() string
Path() ObjectPath
Expand All @@ -35,23 +37,65 @@ func (o *Object) CallWithContext(ctx context.Context, method string, flags Flags
return <-o.createCall(ctx, method, flags, make(chan *Call, 1), args...).Done
}

// AddMatchSignal subscribes BusObject to signals from specified interface and
// method (member).
func (o *Object) AddMatchSignal(iface, member string) *Call {
return o.Call(
// MatchOption specifies option for dbus routing match rule. Options can be constructed with WithMatch* helpers.
// For full list of available options consult
// https://dbus.freedesktop.org/doc/dbus-specification.html#message-bus-routing-match-rules
type MatchOption struct {
key string
value string
}

// WithMatchOption creates match option with given key and value
func WithMatchOption(key, value string) MatchOption {
return MatchOption{key, value}
}

// WithMatchObjectPath creates match option that filters events based on given path
func WithMatchObjectPath(path ObjectPath) MatchOption {
return MatchOption{"path", string(path)}
}

func formatMatchOptions(options []MatchOption) string {
items := make([]string, 0, len(options))
for _, option := range options {
items = append(items, option.key+"='"+option.value+"'")
}

return strings.Join(items, ",")
}

// AddMatchSignal subscribes BusObject to signals from specified interface,
// method (member). Additional filter rules can be added via WithMatch* option constructors.
// Note: To filter events by object path you have to specify this path via an option.
func (o *Object) AddMatchSignal(iface, member string, options ...MatchOption) *Call {
base := []MatchOption{
{"type", "signal"},
{"interface", iface},
{"member", member},
}

options = append(base, options...)
return o.conn.BusObject().Call(
"org.freedesktop.DBus.AddMatch",
0,
"type='signal',interface='"+iface+"',member='"+member+"'",
formatMatchOptions(options),
)
}

// RemoveMatchSignal unsubscribes BusObject to signals from specified interface and
// method (member).
func (o *Object) RemoveMatchSignal(iface, member string) *Call {
return o.Call(
// RemoveMatchSignal unsubscribes BusObject from signals from specified interface,
// method (member). Additional filter rules can be added via WithMatch* option constructors
func (o *Object) RemoveMatchSignal(iface, member string, options ...MatchOption) *Call {
base := []MatchOption{
{"type", "signal"},
{"interface", iface},
{"member", member},
}

options = append(base, options...)
return o.conn.BusObject().Call(
"org.freedesktop.DBus.RemoveMatch",
0,
"type='signal',interface='"+iface+"',member='"+member+"'",
formatMatchOptions(options),
)
}

Expand Down
91 changes: 91 additions & 0 deletions object_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,94 @@ func TestObjectGoWithContext(t *testing.T) {
t.Fatal("Expected call to respond in 1 Millisecond")
}
}

type nopServer struct{}

func (_ nopServer) Nop() *Error {
return nil
}

func fetchSignal(t *testing.T, ch chan *Signal, timeout time.Duration) *Signal {
select {
case sig := <-ch:
return sig
case <-time.After(timeout):
t.Fatalf("Failed to fetch signal in specified timeout %s", timeout)
}
return nil
}

func TestObjectSignalHandling(t *testing.T) {
bus, err := SessionBus()
if err != nil {
t.Fatalf("Unexpected error connecting to session bus: %s", err)
}

name := bus.Names()[0]
path := ObjectPath("/org/godbus/DBus/TestSignals")
otherPath := ObjectPath("/org/other-godbus/DBus/TestSignals")
iface := "org.godbus.DBus.TestSignals"
otherIface := "org.godbus.DBus.OtherTestSignals"
err = bus.Export(nopServer{}, path, iface)
if err != nil {
t.Fatalf("Unexpected error registering nop server: %v", err)
}

obj := bus.Object(name, path)
obj.AddMatchSignal(iface, "Heartbeat", WithMatchObjectPath(obj.Path()))

ch := make(chan *Signal, 5)
bus.Signal(ch)

go func() {
defer func() {
if err := recover(); err != nil {
t.Errorf("Catched panic in emitter goroutine: %v", err)
}
}()

// desired signals
bus.Emit(path, iface+".Heartbeat", uint32(1))
bus.Emit(path, iface+".Heartbeat", uint32(2))
// undesired signals
bus.Emit(otherPath, iface+".Heartbeat", uint32(3))
bus.Emit(otherPath, otherIface+".Heartbeat", uint32(4))
bus.Emit(path, iface+".Updated", false)
// sentinel
bus.Emit(path, iface+".Heartbeat", uint32(5))

time.Sleep(100 * time.Millisecond)
bus.Emit(path, iface+".Heartbeat", uint32(6))
}()

checkSignal := func(sig *Signal, value uint32) {
if sig.Path != path {
t.Errorf("signal.Path mismatch: %s != %s", path, sig.Path)
}

name := iface + ".Heartbeat"
if sig.Name != name {
t.Errorf("signal.Name mismatch: %s != %s", name, sig.Name)
}

if len(sig.Body) != 1 {
t.Errorf("Invalid signal body length: %d", len(sig.Body))
return
}

if sig.Body[0] != interface{}(value) {
t.Errorf("signal value mismatch: %d != %d", value, sig.Body[0])
}
}

checkSignal(fetchSignal(t, ch, 50*time.Millisecond), 1)
checkSignal(fetchSignal(t, ch, 50*time.Millisecond), 2)
checkSignal(fetchSignal(t, ch, 50*time.Millisecond), 5)

obj.RemoveMatchSignal(iface, "Heartbeat", WithMatchObjectPath(obj.Path()))
select {
case sig := <-ch:
t.Errorf("Got signal after removing subscription: %v", sig)
case <-time.After(200 * time.Millisecond):
}
}

0 comments on commit 66d97ae

Please sign in to comment.