-
Notifications
You must be signed in to change notification settings - Fork 51
/
proxy_object.rb
176 lines (157 loc) · 5.87 KB
/
proxy_object.rb
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# frozen_string_literal: true
# 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
# Represents 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 corresponding
# 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.
# @return [Boolean]
attr_accessor :introspected
# The (remote) destination of the object.
attr_reader :destination
# The path to the object.
# @return [ObjectPath]
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.
# @return [Array<String>] names of the interfaces
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)
unless @default_iface && has_iface?(@default_iface)
raise NoMethodError, "undefined signal `#{name}' for DBus interface `#{@default_iface}' on object `#{@path}'"
end
@interfaces[@default_iface].on_signal(name, &block)
end
####################################################
private
# rubocop:disable Lint/MissingSuper
# as this should forward everything
#
# https://github.com/rubocop-hq/ruby-style-guide#no-method-missing
# and http://blog.marc-andre.ca/2010/11/15/methodmissing-politely/
# have a point to be investigated
# 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
# rubocop:enable Lint/MissingSuper
def respond_to_missing?(name, _include_private = false)
@default_iface &&
has_iface?(@default_iface) &&
@interfaces[@default_iface].methods.key?(name) or super
end
end
end