Skip to content

Commit

Permalink
Merge pull request guelfey#27 from kyrofa/feature/20/export_with_map
Browse files Browse the repository at this point in the history
Add ExportWithMap method.
  • Loading branch information
bcwaldon committed Jul 29, 2015
2 parents a61138d + a3eaefb commit 993fdba
Show file tree
Hide file tree
Showing 3 changed files with 185 additions and 11 deletions.
4 changes: 2 additions & 2 deletions conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ type Conn struct {
calls map[uint32]*Call
callsLck sync.RWMutex

handlers map[ObjectPath]map[string]interface{}
handlers map[ObjectPath]map[string]exportWithMapping
handlersLck sync.RWMutex

out chan *Message
Expand Down Expand Up @@ -157,7 +157,7 @@ func newConn(tr transport) (*Conn, error) {
conn.transport = tr
conn.calls = make(map[uint32]*Call)
conn.out = make(chan *Message, 10)
conn.handlers = make(map[ObjectPath]map[string]interface{})
conn.handlers = make(map[ObjectPath]map[string]exportWithMapping)
conn.nextSerial = 1
conn.serialUsed = map[uint32]bool{0: true}
conn.busObj = conn.Object("org.freedesktop.DBus", "/org/freedesktop/DBus")
Expand Down
70 changes: 61 additions & 9 deletions export.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package dbus

import (
"errors"
"fmt"
"reflect"
"strings"
"unicode"
)

var (
Expand All @@ -22,16 +22,49 @@ var (
}
)

// exportWithMapping represents an exported struct along with a method name
// mapping to allow for exporting lower-case methods, etc.
type exportWithMapping struct {
export interface{}

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

// Sender is a type which can be used in exported methods to receive the message
// sender.
type Sender string

func exportedMethod(v interface{}, name string) reflect.Value {
if v == nil {
func exportedMethod(export exportWithMapping, name string) reflect.Value {
if export.export == nil {
return reflect.Value{}
}
m := reflect.ValueOf(v).MethodByName(name)
if !m.IsValid() {

// If a mapping was included in the export, check the map to see if we
// should be looking for a different method in the export.
if export.mapping != nil {
for key, value := range export.mapping {
if value == name {
name = key
break
}

// Catch the case where a method is aliased but the client is calling
// the original, e.g. the "Foo" method was exported mapped to
// "foo," and dbus client called the original "Foo."
if key == name {
return reflect.Value{}
}
}
}

value := reflect.ValueOf(export.export)
m := value.MethodByName(name)

// Catch the case of attempting to call an unexported method
method, ok := value.Type().MethodByName(name)

if !m.IsValid() || !ok || method.PkgPath != "" {
return reflect.Value{}
}
t := m.Type()
Expand Down Expand Up @@ -62,7 +95,7 @@ func (conn *Conn) handleCall(msg *Message) {
}
return
}
if len(name) == 0 || unicode.IsLower([]rune(name)[0]) {
if len(name) == 0 {
conn.sendError(errmsgUnknownMethod, sender, serial)
}
var m reflect.Value
Expand Down Expand Up @@ -214,24 +247,43 @@ func (conn *Conn) Emit(path ObjectPath, name string, values ...interface{}) erro
//
// Export returns an error if path is not a valid path name.
func (conn *Conn) Export(v interface{}, path ObjectPath, iface string) error {
return conn.ExportWithMap(v, nil, path, iface)
}

// ExportWithMap works exactly like Export 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) ExportWithMap(v interface{}, mapping map[string]string, path ObjectPath, iface string) error {
if !path.IsValid() {
return errors.New("dbus: invalid path name")
return fmt.Errorf(`dbus: Invalid path name: "%s"`, path)
}

conn.handlersLck.Lock()
defer conn.handlersLck.Unlock()

// Remove a previous export if the interface is nil
if v == nil {
if _, ok := conn.handlers[path]; ok {
delete(conn.handlers[path], iface)
if len(conn.handlers[path]) == 0 {
delete(conn.handlers, path)
}
}

return nil
}

// If this is the first handler for this path, make a new map to hold all
// handlers for this path.
if _, ok := conn.handlers[path]; !ok {
conn.handlers[path] = make(map[string]interface{})
conn.handlers[path] = make(map[string]exportWithMapping)
}
conn.handlers[path][iface] = v

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

return nil
}

Expand Down
122 changes: 122 additions & 0 deletions export_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package dbus

import "testing"

type lowerCaseExport struct{}

func (export lowerCaseExport) foo() (string, *Error) {
return "bar", nil
}

// Test typical Export usage.
func TestExport(t *testing.T) {
connection, err := SessionBus()
if err != nil {
t.Fatalf("Unexpected error connecting to session bus: %s", err)
}

name := connection.Names()[0]

connection.Export(server{}, "/org/guelfey/DBus/Test", "org.guelfey.DBus.Test")
object := connection.Object(name, "/org/guelfey/DBus/Test")

var response int64
err = object.Call("org.guelfey.DBus.Test.Double", 0, int64(2)).Store(&response)
if err != nil {
t.Errorf("Unexpected error calling Double: %s", err)
}

if response != 4 {
t.Errorf("Response was %d, expected 4", response)
}

// Now remove export
connection.Export(nil, "/org/guelfey/DBus/Test", "org.guelfey.DBus.Test")
err = object.Call("org.guelfey.DBus.Test.Double", 0, int64(2)).Store(&response)
if err == nil {
t.Error("Expected an error since the export was removed")
}
}

// Test Export with an invalid path.
func TestExport_invalidPath(t *testing.T) {
connection, err := SessionBus()
if err != nil {
t.Fatalf("Unexpected error connecting to session bus: %s", err)
}

err = connection.Export(nil, "foo", "bar")
if err == nil {
t.Error("Expected an error due to exporting with an invalid path")
}
}

// Test Export with an un-exported method. This should not panic, but rather
// result in an invalid method call.
func TestExport_unexportedMethod(t *testing.T) {
connection, err := SessionBus()
if err != nil {
t.Fatalf("Unexpected error connecting to session bus: %s", err)
}

name := connection.Names()[0]

connection.Export(lowerCaseExport{}, "/org/guelfey/DBus/Test", "org.guelfey.DBus.Test")
object := connection.Object(name, "/org/guelfey/DBus/Test")

var response string
call := object.Call("org.guelfey.DBus.Test.foo", 0)
err = call.Store(&response)
if err == nil {
t.Errorf("Expected an error due to calling unexported method")
}
}

// Test typical ExportWithMap usage.
func TestExportWithMap(t *testing.T) {
connection, err := SessionBus()
if err != nil {
t.Fatalf("Unexpected error connecting to session bus: %s", err)
}

name := connection.Names()[0]

mapping := make(map[string]string)
mapping["Double"] = "double" // Export this method as lower-case

connection.ExportWithMap(server{}, mapping, "/org/guelfey/DBus/Test", "org.guelfey.DBus.Test")
object := connection.Object(name, "/org/guelfey/DBus/Test")

var response int64
err = object.Call("org.guelfey.DBus.Test.double", 0, int64(2)).Store(&response)
if err != nil {
t.Errorf("Unexpected error calling double: %s", err)
}

if response != 4 {
t.Errorf("Response was %d, expected 4", response)
}
}

// Test that ExportWithMap does not export both method alias and method.
func TestExportWithMap_bypassAlias(t *testing.T) {
connection, err := SessionBus()
if err != nil {
t.Fatalf("Unexpected error connecting to session bus: %s", err)
}

name := connection.Names()[0]

mapping := make(map[string]string)
mapping["Double"] = "double" // Export this method as lower-case

connection.ExportWithMap(server{}, mapping, "/org/guelfey/DBus/Test", "org.guelfey.DBus.Test")
object := connection.Object(name, "/org/guelfey/DBus/Test")

var response int64
// Call upper-case Double (i.e. the real method, not the alias)
err = object.Call("org.guelfey.DBus.Test.Double", 0, int64(2)).Store(&response)
if err == nil {
t.Error("Expected an error due to calling actual method, not alias")
}
}

0 comments on commit 993fdba

Please sign in to comment.