gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

export.go (13933B)


      1 package dbus
      2 
      3 import (
      4 	"errors"
      5 	"fmt"
      6 	"reflect"
      7 	"strings"
      8 )
      9 
     10 var (
     11 	ErrMsgInvalidArg = Error{
     12 		"org.freedesktop.DBus.Error.InvalidArgs",
     13 		[]interface{}{"Invalid type / number of args"},
     14 	}
     15 	ErrMsgNoObject = Error{
     16 		"org.freedesktop.DBus.Error.NoSuchObject",
     17 		[]interface{}{"No such object"},
     18 	}
     19 	ErrMsgUnknownMethod = Error{
     20 		"org.freedesktop.DBus.Error.UnknownMethod",
     21 		[]interface{}{"Unknown / invalid method"},
     22 	}
     23 	ErrMsgUnknownInterface = Error{
     24 		"org.freedesktop.DBus.Error.UnknownInterface",
     25 		[]interface{}{"Object does not implement the interface"},
     26 	}
     27 )
     28 
     29 func MakeFailedError(err error) *Error {
     30 	return &Error{
     31 		"org.freedesktop.DBus.Error.Failed",
     32 		[]interface{}{err.Error()},
     33 	}
     34 }
     35 
     36 // Sender is a type which can be used in exported methods to receive the message
     37 // sender.
     38 type Sender string
     39 
     40 func computeMethodName(name string, mapping map[string]string) string {
     41 	newname, ok := mapping[name]
     42 	if ok {
     43 		name = newname
     44 	}
     45 	return name
     46 }
     47 
     48 func getMethods(in interface{}, mapping map[string]string) map[string]reflect.Value {
     49 	if in == nil {
     50 		return nil
     51 	}
     52 	methods := make(map[string]reflect.Value)
     53 	val := reflect.ValueOf(in)
     54 	typ := val.Type()
     55 	for i := 0; i < typ.NumMethod(); i++ {
     56 		methtype := typ.Method(i)
     57 		method := val.Method(i)
     58 		t := method.Type()
     59 		// only track valid methods must return *Error as last arg
     60 		// and must be exported
     61 		if t.NumOut() == 0 ||
     62 			t.Out(t.NumOut()-1) != reflect.TypeOf(&ErrMsgInvalidArg) ||
     63 			methtype.PkgPath != "" {
     64 			continue
     65 		}
     66 		// map names while building table
     67 		methods[computeMethodName(methtype.Name, mapping)] = method
     68 	}
     69 	return methods
     70 }
     71 
     72 func getAllMethods(in interface{}, mapping map[string]string) map[string]reflect.Value {
     73 	if in == nil {
     74 		return nil
     75 	}
     76 	methods := make(map[string]reflect.Value)
     77 	val := reflect.ValueOf(in)
     78 	typ := val.Type()
     79 	for i := 0; i < typ.NumMethod(); i++ {
     80 		methtype := typ.Method(i)
     81 		method := val.Method(i)
     82 		// map names while building table
     83 		methods[computeMethodName(methtype.Name, mapping)] = method
     84 	}
     85 	return methods
     86 }
     87 
     88 func standardMethodArgumentDecode(m Method, sender string, msg *Message, body []interface{}) ([]interface{}, error) {
     89 	pointers := make([]interface{}, m.NumArguments())
     90 	decode := make([]interface{}, 0, len(body))
     91 
     92 	for i := 0; i < m.NumArguments(); i++ {
     93 		tp := reflect.TypeOf(m.ArgumentValue(i))
     94 		val := reflect.New(tp)
     95 		pointers[i] = val.Interface()
     96 		if tp == reflect.TypeOf((*Sender)(nil)).Elem() {
     97 			val.Elem().SetString(sender)
     98 		} else if tp == reflect.TypeOf((*Message)(nil)).Elem() {
     99 			val.Elem().Set(reflect.ValueOf(*msg))
    100 		} else {
    101 			decode = append(decode, pointers[i])
    102 		}
    103 	}
    104 
    105 	if len(decode) != len(body) {
    106 		return nil, ErrMsgInvalidArg
    107 	}
    108 
    109 	if err := Store(body, decode...); err != nil {
    110 		return nil, ErrMsgInvalidArg
    111 	}
    112 
    113 	return pointers, nil
    114 }
    115 
    116 func (conn *Conn) decodeArguments(m Method, sender string, msg *Message) ([]interface{}, error) {
    117 	if decoder, ok := m.(ArgumentDecoder); ok {
    118 		return decoder.DecodeArguments(conn, sender, msg, msg.Body)
    119 	}
    120 	return standardMethodArgumentDecode(m, sender, msg, msg.Body)
    121 }
    122 
    123 // handleCall handles the given method call (i.e. looks if it's one of the
    124 // pre-implemented ones and searches for a corresponding handler if not).
    125 func (conn *Conn) handleCall(msg *Message) {
    126 	name := msg.Headers[FieldMember].value.(string)
    127 	path := msg.Headers[FieldPath].value.(ObjectPath)
    128 	ifaceName, _ := msg.Headers[FieldInterface].value.(string)
    129 	sender, hasSender := msg.Headers[FieldSender].value.(string)
    130 	serial := msg.serial
    131 	if ifaceName == "org.freedesktop.DBus.Peer" {
    132 		switch name {
    133 		case "Ping":
    134 			conn.sendReply(sender, serial)
    135 		case "GetMachineId":
    136 			conn.sendReply(sender, serial, conn.uuid)
    137 		default:
    138 			conn.sendError(ErrMsgUnknownMethod, sender, serial)
    139 		}
    140 		return
    141 	}
    142 	if len(name) == 0 {
    143 		conn.sendError(ErrMsgUnknownMethod, sender, serial)
    144 	}
    145 
    146 	object, ok := conn.handler.LookupObject(path)
    147 	if !ok {
    148 		conn.sendError(ErrMsgNoObject, sender, serial)
    149 		return
    150 	}
    151 
    152 	iface, exists := object.LookupInterface(ifaceName)
    153 	if !exists {
    154 		conn.sendError(ErrMsgUnknownInterface, sender, serial)
    155 		return
    156 	}
    157 
    158 	m, exists := iface.LookupMethod(name)
    159 	if !exists {
    160 		conn.sendError(ErrMsgUnknownMethod, sender, serial)
    161 		return
    162 	}
    163 	args, err := conn.decodeArguments(m, sender, msg)
    164 	if err != nil {
    165 		conn.sendError(err, sender, serial)
    166 		return
    167 	}
    168 
    169 	ret, err := m.Call(args...)
    170 	if err != nil {
    171 		conn.sendError(err, sender, serial)
    172 		return
    173 	}
    174 
    175 	if msg.Flags&FlagNoReplyExpected == 0 {
    176 		reply := new(Message)
    177 		reply.Type = TypeMethodReply
    178 		reply.Headers = make(map[HeaderField]Variant)
    179 		if hasSender {
    180 			reply.Headers[FieldDestination] = msg.Headers[FieldSender]
    181 		}
    182 		reply.Headers[FieldReplySerial] = MakeVariant(msg.serial)
    183 		reply.Body = make([]interface{}, len(ret))
    184 		for i := 0; i < len(ret); i++ {
    185 			reply.Body[i] = ret[i]
    186 		}
    187 		reply.Headers[FieldSignature] = MakeVariant(SignatureOf(reply.Body...))
    188 
    189 		conn.sendMessageAndIfClosed(reply, nil)
    190 	}
    191 }
    192 
    193 // Emit emits the given signal on the message bus. The name parameter must be
    194 // formatted as "interface.member", e.g., "org.freedesktop.DBus.NameLost".
    195 func (conn *Conn) Emit(path ObjectPath, name string, values ...interface{}) error {
    196 	if !path.IsValid() {
    197 		return errors.New("dbus: invalid object path")
    198 	}
    199 	i := strings.LastIndex(name, ".")
    200 	if i == -1 {
    201 		return errors.New("dbus: invalid method name")
    202 	}
    203 	iface := name[:i]
    204 	member := name[i+1:]
    205 	if !isValidMember(member) {
    206 		return errors.New("dbus: invalid method name")
    207 	}
    208 	if !isValidInterface(iface) {
    209 		return errors.New("dbus: invalid interface name")
    210 	}
    211 	msg := new(Message)
    212 	msg.Type = TypeSignal
    213 	msg.Headers = make(map[HeaderField]Variant)
    214 	msg.Headers[FieldInterface] = MakeVariant(iface)
    215 	msg.Headers[FieldMember] = MakeVariant(member)
    216 	msg.Headers[FieldPath] = MakeVariant(path)
    217 	msg.Body = values
    218 	if len(values) > 0 {
    219 		msg.Headers[FieldSignature] = MakeVariant(SignatureOf(values...))
    220 	}
    221 
    222 	var closed bool
    223 	conn.sendMessageAndIfClosed(msg, func() {
    224 		closed = true
    225 	})
    226 	if closed {
    227 		return ErrClosed
    228 	}
    229 	return nil
    230 }
    231 
    232 // Export registers the given value to be exported as an object on the
    233 // message bus.
    234 //
    235 // If a method call on the given path and interface is received, an exported
    236 // method with the same name is called with v as the receiver if the
    237 // parameters match and the last return value is of type *Error. If this
    238 // *Error is not nil, it is sent back to the caller as an error.
    239 // Otherwise, a method reply is sent with the other return values as its body.
    240 //
    241 // Any parameters with the special type Sender are set to the sender of the
    242 // dbus message when the method is called. Parameters of this type do not
    243 // contribute to the dbus signature of the method (i.e. the method is exposed
    244 // as if the parameters of type Sender were not there).
    245 //
    246 // Similarly, any parameters with the type Message are set to the raw message
    247 // received on the bus. Again, parameters of this type do not contribute to the
    248 // dbus signature of the method.
    249 //
    250 // Every method call is executed in a new goroutine, so the method may be called
    251 // in multiple goroutines at once.
    252 //
    253 // Method calls on the interface org.freedesktop.DBus.Peer will be automatically
    254 // handled for every object.
    255 //
    256 // Passing nil as the first parameter will cause conn to cease handling calls on
    257 // the given combination of path and interface.
    258 //
    259 // Export returns an error if path is not a valid path name.
    260 func (conn *Conn) Export(v interface{}, path ObjectPath, iface string) error {
    261 	return conn.ExportWithMap(v, nil, path, iface)
    262 }
    263 
    264 // ExportAll registers all exported methods defined by the given object on
    265 // the message bus.
    266 //
    267 // Unlike Export there is no requirement to have the last parameter as type
    268 // *Error. If you want to be able to return error then you can append an error
    269 // type parameter to your method signature. If the error returned is not nil,
    270 // it is sent back to the caller as an error. Otherwise, a method reply is
    271 // sent with the other return values as its body.
    272 func (conn *Conn) ExportAll(v interface{}, path ObjectPath, iface string) error {
    273 	return conn.export(getAllMethods(v, nil), path, iface, false)
    274 }
    275 
    276 // ExportWithMap works exactly like Export but provides the ability to remap
    277 // method names (e.g. export a lower-case method).
    278 //
    279 // The keys in the map are the real method names (exported on the struct), and
    280 // the values are the method names to be exported on DBus.
    281 func (conn *Conn) ExportWithMap(v interface{}, mapping map[string]string, path ObjectPath, iface string) error {
    282 	return conn.export(getMethods(v, mapping), path, iface, false)
    283 }
    284 
    285 // ExportSubtree works exactly like Export but registers the given value for
    286 // an entire subtree rather under the root path provided.
    287 //
    288 // In order to make this useful, one parameter in each of the value's exported
    289 // methods should be a Message, in which case it will contain the raw message
    290 // (allowing one to get access to the path that caused the method to be called).
    291 //
    292 // Note that more specific export paths take precedence over less specific. For
    293 // example, a method call using the ObjectPath /foo/bar/baz will call a method
    294 // exported on /foo/bar before a method exported on /foo.
    295 func (conn *Conn) ExportSubtree(v interface{}, path ObjectPath, iface string) error {
    296 	return conn.ExportSubtreeWithMap(v, nil, path, iface)
    297 }
    298 
    299 // ExportSubtreeWithMap works exactly like ExportSubtree but provides the
    300 // ability to remap method names (e.g. export a lower-case method).
    301 //
    302 // The keys in the map are the real method names (exported on the struct), and
    303 // the values are the method names to be exported on DBus.
    304 func (conn *Conn) ExportSubtreeWithMap(v interface{}, mapping map[string]string, path ObjectPath, iface string) error {
    305 	return conn.export(getMethods(v, mapping), path, iface, true)
    306 }
    307 
    308 // ExportMethodTable like Export registers the given methods as an object
    309 // on the message bus. Unlike Export the it uses a method table to define
    310 // the object instead of a native go object.
    311 //
    312 // The method table is a map from method name to function closure
    313 // representing the method. This allows an object exported on the bus to not
    314 // necessarily be a native go object. It can be useful for generating exposed
    315 // methods on the fly.
    316 //
    317 // Any non-function objects in the method table are ignored.
    318 func (conn *Conn) ExportMethodTable(methods map[string]interface{}, path ObjectPath, iface string) error {
    319 	return conn.exportMethodTable(methods, path, iface, false)
    320 }
    321 
    322 // Like ExportSubtree, but with the same caveats as ExportMethodTable.
    323 func (conn *Conn) ExportSubtreeMethodTable(methods map[string]interface{}, path ObjectPath, iface string) error {
    324 	return conn.exportMethodTable(methods, path, iface, true)
    325 }
    326 
    327 func (conn *Conn) exportMethodTable(methods map[string]interface{}, path ObjectPath, iface string, includeSubtree bool) error {
    328 	var out map[string]reflect.Value
    329 	if methods != nil {
    330 		out = make(map[string]reflect.Value)
    331 		for name, method := range methods {
    332 			rval := reflect.ValueOf(method)
    333 			if rval.Kind() != reflect.Func {
    334 				continue
    335 			}
    336 			t := rval.Type()
    337 			// only track valid methods must return *Error as last arg
    338 			if t.NumOut() == 0 ||
    339 				t.Out(t.NumOut()-1) != reflect.TypeOf(&ErrMsgInvalidArg) {
    340 				continue
    341 			}
    342 			out[name] = rval
    343 		}
    344 	}
    345 	return conn.export(out, path, iface, includeSubtree)
    346 }
    347 
    348 func (conn *Conn) unexport(h *defaultHandler, path ObjectPath, iface string) error {
    349 	if h.PathExists(path) {
    350 		obj := h.objects[path]
    351 		obj.DeleteInterface(iface)
    352 		if len(obj.interfaces) == 0 {
    353 			h.DeleteObject(path)
    354 		}
    355 	}
    356 	return nil
    357 }
    358 
    359 // export is the worker function for all exports/registrations.
    360 func (conn *Conn) export(methods map[string]reflect.Value, path ObjectPath, iface string, includeSubtree bool) error {
    361 	h, ok := conn.handler.(*defaultHandler)
    362 	if !ok {
    363 		return fmt.Errorf(
    364 			`dbus: export only allowed on the default handler. Received: %T"`,
    365 			conn.handler)
    366 	}
    367 
    368 	if !path.IsValid() {
    369 		return fmt.Errorf(`dbus: Invalid path name: "%s"`, path)
    370 	}
    371 
    372 	// Remove a previous export if the interface is nil
    373 	if methods == nil {
    374 		return conn.unexport(h, path, iface)
    375 	}
    376 
    377 	// If this is the first handler for this path, make a new map to hold all
    378 	// handlers for this path.
    379 	if !h.PathExists(path) {
    380 		h.AddObject(path, newExportedObject())
    381 	}
    382 
    383 	exportedMethods := make(map[string]Method)
    384 	for name, method := range methods {
    385 		exportedMethods[name] = exportedMethod{method}
    386 	}
    387 
    388 	// Finally, save this handler
    389 	obj := h.objects[path]
    390 	obj.AddInterface(iface, newExportedIntf(exportedMethods, includeSubtree))
    391 
    392 	return nil
    393 }
    394 
    395 // ReleaseName calls org.freedesktop.DBus.ReleaseName and awaits a response.
    396 func (conn *Conn) ReleaseName(name string) (ReleaseNameReply, error) {
    397 	var r uint32
    398 	err := conn.busObj.Call("org.freedesktop.DBus.ReleaseName", 0, name).Store(&r)
    399 	if err != nil {
    400 		return 0, err
    401 	}
    402 	return ReleaseNameReply(r), nil
    403 }
    404 
    405 // RequestName calls org.freedesktop.DBus.RequestName and awaits a response.
    406 func (conn *Conn) RequestName(name string, flags RequestNameFlags) (RequestNameReply, error) {
    407 	var r uint32
    408 	err := conn.busObj.Call("org.freedesktop.DBus.RequestName", 0, name, flags).Store(&r)
    409 	if err != nil {
    410 		return 0, err
    411 	}
    412 	return RequestNameReply(r), nil
    413 }
    414 
    415 // ReleaseNameReply is the reply to a ReleaseName call.
    416 type ReleaseNameReply uint32
    417 
    418 const (
    419 	ReleaseNameReplyReleased ReleaseNameReply = 1 + iota
    420 	ReleaseNameReplyNonExistent
    421 	ReleaseNameReplyNotOwner
    422 )
    423 
    424 // RequestNameFlags represents the possible flags for a RequestName call.
    425 type RequestNameFlags uint32
    426 
    427 const (
    428 	NameFlagAllowReplacement RequestNameFlags = 1 << iota
    429 	NameFlagReplaceExisting
    430 	NameFlagDoNotQueue
    431 )
    432 
    433 // RequestNameReply is the reply to a RequestName call.
    434 type RequestNameReply uint32
    435 
    436 const (
    437 	RequestNameReplyPrimaryOwner RequestNameReply = 1 + iota
    438 	RequestNameReplyInQueue
    439 	RequestNameReplyExists
    440 	RequestNameReplyAlreadyOwner
    441 )