Skip to content

Commit

Permalink
Merge pull request #35 from kyrofa/feature/34/subtree_support
Browse files Browse the repository at this point in the history
Add ExportSubtree functions for subtree support.
  • Loading branch information
philips committed Sep 10, 2015
2 parents e5cbcd1 + 30c065f commit 9bb9dbf
Show file tree
Hide file tree
Showing 3 changed files with 347 additions and 19 deletions.
111 changes: 93 additions & 18 deletions export.go
Expand Up @@ -29,6 +29,9 @@ type exportWithMapping struct {

// Method name mapping; key -> struct method, value -> dbus method.
mapping map[string]string

// Whether or not this export is for the entire subtree
includeSubtree bool
}

// Sender is a type which can be used in exported methods to receive the message
Expand Down Expand Up @@ -76,6 +79,42 @@ func exportedMethod(export exportWithMapping, name string) reflect.Value {
return m
}

// searchHandlers will look through all registered handlers looking for one
// to handle the given path. If a verbatim one isn't found, it will check for
// a subtree registration for the path as well.
func (conn *Conn) searchHandlers(path ObjectPath) (map[string]exportWithMapping, bool) {
conn.handlersLck.RLock()
defer conn.handlersLck.RUnlock()

handlers, ok := conn.handlers[path]
if ok {
return handlers, ok
}

// If handlers weren't found for this exact path, look for a matching subtree
// registration
handlers = make(map[string]exportWithMapping)
path = path[:strings.LastIndex(string(path), "/")]
for len(path) > 0 {
var subtreeHandlers map[string]exportWithMapping
subtreeHandlers, ok = conn.handlers[path]
if ok {
for iface, handler := range subtreeHandlers {
// Only include this handler if it registered for the subtree
if handler.includeSubtree {
handlers[iface] = handler
}
}

break
}

path = path[:strings.LastIndex(string(path), "/")]
}

return handlers, ok
}

// handleCall handles the given method call (i.e. looks if it's one of the
// pre-implemented ones and searches for a corresponding handler if not).
func (conn *Conn) handleCall(msg *Message) {
Expand All @@ -98,37 +137,32 @@ func (conn *Conn) handleCall(msg *Message) {
if len(name) == 0 {
conn.sendError(errmsgUnknownMethod, sender, serial)
}

// Find the exported handler (if any) for this path
handlers, ok := conn.searchHandlers(path)
if !ok {
conn.sendError(errmsgNoObject, sender, serial)
return
}

var m reflect.Value
if hasIface {
conn.handlersLck.RLock()
obj, ok := conn.handlers[path]
if !ok {
conn.sendError(errmsgNoObject, sender, serial)
conn.handlersLck.RUnlock()
return
}
iface := obj[ifaceName]
conn.handlersLck.RUnlock()
iface := handlers[ifaceName]
m = exportedMethod(iface, name)
} else {
conn.handlersLck.RLock()
if _, ok := conn.handlers[path]; !ok {
conn.sendError(errmsgNoObject, sender, serial)
conn.handlersLck.RUnlock()
return
}
for _, v := range conn.handlers[path] {
for _, v := range handlers {
m = exportedMethod(v, name)
if m.IsValid() {
break
}
}
conn.handlersLck.RUnlock()
}

if !m.IsValid() {
conn.sendError(errmsgUnknownMethod, sender, serial)
return
}

t := m.Type()
vs := msg.Body
pointers := make([]interface{}, t.NumIn())
Expand All @@ -139,27 +173,36 @@ func (conn *Conn) handleCall(msg *Message) {
pointers[i] = val.Interface()
if tp == reflect.TypeOf((*Sender)(nil)).Elem() {
val.Elem().SetString(sender)
} else if tp == reflect.TypeOf((*Message)(nil)).Elem() {
val.Elem().Set(reflect.ValueOf(*msg))
} else {
decode = append(decode, pointers[i])
}
}

if len(decode) != len(vs) {
conn.sendError(errmsgInvalidArg, sender, serial)
return
}

if err := Store(vs, decode...); err != nil {
conn.sendError(errmsgInvalidArg, sender, serial)
return
}

// Extract parameters
params := make([]reflect.Value, len(pointers))
for i := 0; i < len(pointers); i++ {
params[i] = reflect.ValueOf(pointers[i]).Elem()
}

// Call method
ret := m.Call(params)
if em := ret[t.NumOut()-1].Interface().(*Error); em != nil {
conn.sendError(*em, sender, serial)
return
}

if msg.Flags&FlagNoReplyExpected == 0 {
reply := new(Message)
reply.Type = TypeMethodReply
Expand Down Expand Up @@ -236,6 +279,10 @@ func (conn *Conn) Emit(path ObjectPath, name string, values ...interface{}) erro
// contribute to the dbus signature of the method (i.e. the method is exposed
// as if the parameters of type Sender were not there).
//
// Similarly, any parameters with the type Message are set to the raw message
// received on the bus. Again, parameters of this type do not contribute to the
// dbus signature of the method.
//
// Every method call is executed in a new goroutine, so the method may be called
// in multiple goroutines at once.
//
Expand All @@ -256,6 +303,34 @@ func (conn *Conn) Export(v interface{}, path ObjectPath, iface string) error {
// The keys in the map are the real method names (exported on the struct), and
// the values are the method names to be exported on DBus.
func (conn *Conn) ExportWithMap(v interface{}, mapping map[string]string, path ObjectPath, iface string) error {
return conn.exportWithMap(v, mapping, path, iface, false)
}

// ExportSubtree works exactly like Export but registers the given value for
// an entire subtree rather under the root path provided.
//
// In order to make this useful, one parameter in each of the value's exported
// methods should be a Message, in which case it will contain the raw message
// (allowing one to get access to the path that caused the method to be called).
//
// Note that more specific export paths take precedence over less specific. For
// example, a method call using the ObjectPath /foo/bar/baz will call a method
// exported on /foo/bar before a method exported on /foo.
func (conn *Conn) ExportSubtree(v interface{}, path ObjectPath, iface string) error {
return conn.ExportSubtreeWithMap(v, nil, path, iface)
}

// ExportSubtreeWithMap works exactly like ExportSubtree but provides the
// ability to remap method names (e.g. export a lower-case method).
//
// The keys in the map are the real method names (exported on the struct), and
// the values are the method names to be exported on DBus.
func (conn *Conn) ExportSubtreeWithMap(v interface{}, mapping map[string]string, path ObjectPath, iface string) error {
return conn.exportWithMap(v, mapping, path, iface, true)
}

// exportWithMap is the worker function for all exports/registrations.
func (conn *Conn) exportWithMap(v interface{}, mapping map[string]string, path ObjectPath, iface string, includeSubtree bool) error {
if !path.IsValid() {
return fmt.Errorf(`dbus: Invalid path name: "%s"`, path)
}
Expand All @@ -282,7 +357,7 @@ func (conn *Conn) ExportWithMap(v interface{}, mapping map[string]string, path O
}

// Finally, save this handler
conn.handlers[path][iface] = exportWithMapping{export: v, mapping: mapping}
conn.handlers[path][iface] = exportWithMapping{export: v, mapping: mapping, includeSubtree: includeSubtree}

return nil
}
Expand Down

0 comments on commit 9bb9dbf

Please sign in to comment.