1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
|
# This file is part of the ruby-dbus project
# Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
# Copyright (C) 2009-2014 Martin Vidner
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License, version 2.1 as published by the Free Software Foundation.
# See the file "COPYING" for the exact licensing terms.
module DBus
# D-Bus proxy object class
#
# Class representing a remote object in an external application.
# Typically, calling a method on an instance of a ProxyObject sends a message
# over the bus so that the method is executed remotely on the correctponding
# object.
class ProxyObject
# The names of direct subnodes of the object in the tree.
attr_accessor :subnodes
# Flag determining whether the object has been introspected.
attr_accessor :introspected
# The (remote) destination of the object.
attr_reader :destination
# The path to the object.
attr_reader :path
# The bus the object is reachable via.
attr_reader :bus
# @return [String] The name of the default interface of the object.
attr_accessor :default_iface
# @api private
# @return [ApiOptions]
attr_reader :api
# Creates a new proxy object living on the given _bus_ at destination _dest_
# on the given _path_.
def initialize(bus, dest, path, api: ApiOptions::CURRENT)
@bus = bus
@destination = dest
@path = ObjectPath.new(path)
@introspected = false
@interfaces = {}
@subnodes = []
@api = api
end
# Returns the interfaces of the object.
def interfaces
introspect unless introspected
@interfaces.keys
end
# Retrieves an interface of the proxy object
# @param [String] intfname
# @return [ProxyObjectInterface]
def [](intfname)
introspect unless introspected
ifc = @interfaces[intfname]
raise DBus::Error, "no such interface `#{intfname}' on object `#{@path}'" unless ifc
ifc
end
# Maps the given interface name _intfname_ to the given interface _intf.
# @param [String] intfname
# @param [ProxyObjectInterface] intf
# @return [ProxyObjectInterface]
# @api private
def []=(intfname, intf)
@interfaces[intfname] = intf
end
# Introspects the remote object. Allows you to find and select
# interfaces on the object.
def introspect
# Synchronous call here.
xml = @bus.introspect_data(@destination, @path)
ProxyObjectFactory.introspect_into(self, xml)
define_shortcut_methods
xml
end
# For each non duplicated method name in any interface present on the
# caller, defines a shortcut method dynamically.
# This function is automatically called when a {ProxyObject} is
# introspected.
def define_shortcut_methods
# builds a list of duplicated methods
dup_meths = []
univocal_meths = {}
@interfaces.each_value do |intf|
intf.methods.each_value do |meth|
name = meth.name.to_sym
# don't overwrite instance methods!
next if dup_meths.include?(name)
next if self.class.instance_methods.include?(name)
if univocal_meths.include? name
univocal_meths.delete name
dup_meths << name
else
univocal_meths[name] = intf
end
end
end
univocal_meths.each do |name, intf|
# creates a shortcut function that forwards each call to the method on
# the appropriate intf
singleton_class.class_eval do
redefine_method name do |*args, &reply_handler|
intf.method(name).call(*args, &reply_handler)
end
end
end
end
# Returns whether the object has an interface with the given _name_.
def has_iface?(name)
introspect unless introspected
@interfaces.key?(name)
end
# Registers a handler, the code block, for a signal with the given _name_.
# It uses _default_iface_ which must have been set.
# @return [void]
def on_signal(name, &block)
# TODO: improve
raise NoMethodError unless @default_iface && has_iface?(@default_iface)
@interfaces[@default_iface].on_signal(name, &block)
end
####################################################
private
# Handles all unkown methods, mostly to route method calls to the
# default interface.
def method_missing(name, *args, &reply_handler)
unless @default_iface && has_iface?(@default_iface)
# TODO: distinguish:
# - di not specified
# TODO
# - di is specified but not found in introspection data
raise NoMethodError, "undefined method `#{name}' for DBus interface `#{@default_iface}' on object `#{@path}'"
end
begin
@interfaces[@default_iface].method(name).call(*args, &reply_handler)
rescue NameError => e
# interesting, foo.method("unknown")
# raises NameError, not NoMethodError
raise unless e.to_s =~ /undefined method `#{name}'/
# BTW e.exception("...") would preserve the class.
raise NoMethodError, "undefined method `#{name}' for DBus interface `#{@default_iface}' on object `#{@path}'"
end
end
end # class ProxyObject
end
|