circus-deployment 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. data/LICENSE +23 -0
  2. data/README.md +0 -0
  3. data/bin/circus +24 -0
  4. data/lib/bundler/circus_bundler.rb +24 -0
  5. data/lib/bundler/circus_util.rb +43 -0
  6. data/lib/bundler/patches.rb +18 -0
  7. data/lib/circus/act.rb +74 -0
  8. data/lib/circus/actstore_client.rb +30 -0
  9. data/lib/circus/agents/agent.rb +59 -0
  10. data/lib/circus/agents/client.rb +77 -0
  11. data/lib/circus/agents/connection.rb +76 -0
  12. data/lib/circus/agents/conversation.rb +17 -0
  13. data/lib/circus/agents/dbus_connection.rb +85 -0
  14. data/lib/circus/agents/dbus_logger.rb +34 -0
  15. data/lib/circus/agents/encoding.rb +32 -0
  16. data/lib/circus/agents/logger.rb +52 -0
  17. data/lib/circus/agents/params.rb +47 -0
  18. data/lib/circus/agents/ssh_connection.rb +120 -0
  19. data/lib/circus/application.rb +99 -0
  20. data/lib/circus/booth_client.rb +25 -0
  21. data/lib/circus/booth_tool.rb +98 -0
  22. data/lib/circus/cli.rb +147 -0
  23. data/lib/circus/clown_client.rb +27 -0
  24. data/lib/circus/connection_builder.rb +32 -0
  25. data/lib/circus/external_util.rb +14 -0
  26. data/lib/circus/local_config.rb +65 -0
  27. data/lib/circus/profiles/base.rb +115 -0
  28. data/lib/circus/profiles/django.rb +90 -0
  29. data/lib/circus/profiles/jekyll.rb +90 -0
  30. data/lib/circus/profiles/make_base.rb +17 -0
  31. data/lib/circus/profiles/pure_py.rb +46 -0
  32. data/lib/circus/profiles/pure_rb.rb +48 -0
  33. data/lib/circus/profiles/python_base.rb +39 -0
  34. data/lib/circus/profiles/rack.rb +59 -0
  35. data/lib/circus/profiles/ruby_base.rb +52 -0
  36. data/lib/circus/profiles/shell.rb +46 -0
  37. data/lib/circus/profiles.rb +10 -0
  38. data/lib/circus/repos/git.rb +42 -0
  39. data/lib/circus/repos/mercurial.rb +42 -0
  40. data/lib/circus/repos.rb +16 -0
  41. data/lib/circus/resource_allocator_client.rb +19 -0
  42. data/lib/circus/stdout_logger.rb +11 -0
  43. data/lib/circus/version.rb +3 -0
  44. data/lib/circus.rb +9 -0
  45. data/vendor/ruby-dbus/COPYING +504 -0
  46. data/vendor/ruby-dbus/ChangeLog +782 -0
  47. data/vendor/ruby-dbus/HOWTO-RELEASE +14 -0
  48. data/vendor/ruby-dbus/NEWS +104 -0
  49. data/vendor/ruby-dbus/README +53 -0
  50. data/vendor/ruby-dbus/Rakefile +47 -0
  51. data/vendor/ruby-dbus/doc/tutorial/src/00.index.page +12 -0
  52. data/vendor/ruby-dbus/doc/tutorial/src/10.intro.page +127 -0
  53. data/vendor/ruby-dbus/doc/tutorial/src/20.basic_client.page +174 -0
  54. data/vendor/ruby-dbus/doc/tutorial/src/30.service.page +121 -0
  55. data/vendor/ruby-dbus/doc/tutorial/src/default.css +129 -0
  56. data/vendor/ruby-dbus/doc/tutorial/src/default.template +46 -0
  57. data/vendor/ruby-dbus/examples/gdbus/gdbus +255 -0
  58. data/vendor/ruby-dbus/examples/gdbus/gdbus.glade +184 -0
  59. data/vendor/ruby-dbus/examples/gdbus/launch.sh +4 -0
  60. data/vendor/ruby-dbus/examples/no-introspect/nm-test.rb +21 -0
  61. data/vendor/ruby-dbus/examples/no-introspect/tracker-test.rb +16 -0
  62. data/vendor/ruby-dbus/examples/rhythmbox/playpause.rb +25 -0
  63. data/vendor/ruby-dbus/examples/service/call_service.rb +25 -0
  64. data/vendor/ruby-dbus/examples/service/service_newapi.rb +51 -0
  65. data/vendor/ruby-dbus/examples/simple/call_introspect.rb +34 -0
  66. data/vendor/ruby-dbus/examples/utils/listnames.rb +11 -0
  67. data/vendor/ruby-dbus/examples/utils/notify.rb +19 -0
  68. data/vendor/ruby-dbus/lib/dbus/auth.rb +156 -0
  69. data/vendor/ruby-dbus/lib/dbus/bus.rb +750 -0
  70. data/vendor/ruby-dbus/lib/dbus/export.rb +133 -0
  71. data/vendor/ruby-dbus/lib/dbus/introspect.rb +544 -0
  72. data/vendor/ruby-dbus/lib/dbus/marshall.rb +443 -0
  73. data/vendor/ruby-dbus/lib/dbus/matchrule.rb +100 -0
  74. data/vendor/ruby-dbus/lib/dbus/message.rb +293 -0
  75. data/vendor/ruby-dbus/lib/dbus/type.rb +222 -0
  76. data/vendor/ruby-dbus/lib/dbus.rb +89 -0
  77. data/vendor/ruby-dbus/ruby-dbus.gemspec +28 -0
  78. data/vendor/ruby-dbus/setup.rb +1585 -0
  79. data/vendor/ruby-dbus/test/Makefile +4 -0
  80. data/vendor/ruby-dbus/test/bus_driver_test.rb +21 -0
  81. data/vendor/ruby-dbus/test/server_robustness_test.rb +41 -0
  82. data/vendor/ruby-dbus/test/server_test.rb +44 -0
  83. data/vendor/ruby-dbus/test/service_newapi.rb +99 -0
  84. data/vendor/ruby-dbus/test/session_bus_test_manual.rb +20 -0
  85. data/vendor/ruby-dbus/test/signal_test.rb +57 -0
  86. data/vendor/ruby-dbus/test/t1 +4 -0
  87. data/vendor/ruby-dbus/test/t2.rb +66 -0
  88. data/vendor/ruby-dbus/test/t3-ticket27.rb +18 -0
  89. data/vendor/ruby-dbus/test/t5-report-dbus-interface.rb +58 -0
  90. data/vendor/ruby-dbus/test/t6-loop.rb +85 -0
  91. data/vendor/ruby-dbus/test/test_all +26 -0
  92. data/vendor/ruby-dbus/test/test_server +74 -0
  93. data/vendor/ruby-dbus/test/variant_test.rb +66 -0
  94. metadata +225 -0
@@ -0,0 +1,133 @@
1
+ # dbus/introspection.rb - module containing a low-level D-Bus introspection implementation
2
+ #
3
+ # This file is part of the ruby-dbus project
4
+ # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
5
+ #
6
+ # This library is free software; you can redistribute it and/or
7
+ # modify it under the terms of the GNU Lesser General Public
8
+ # License, version 2.1 as published by the Free Software Foundation.
9
+ # See the file "COPYING" for the exact licensing terms.
10
+
11
+ require 'thread'
12
+
13
+ module DBus
14
+ # Exception raised when an interface cannot be found in an object.
15
+ class InterfaceNotInObject < Exception
16
+ end
17
+
18
+ # Exception raised when a method cannot be found in an inferface.
19
+ class MethodNotInInterface < Exception
20
+ end
21
+
22
+ # Method raised when a method returns an invalid return type.
23
+ class InvalidReturnType < Exception
24
+ end
25
+
26
+ # Exported object type
27
+ # = Exportable D-Bus object class
28
+ #
29
+ # Objects that are going to be exported by a D-Bus service
30
+ # should inherit from this class.
31
+ class Object
32
+ # The path of the object.
33
+ attr_reader :path
34
+ # The interfaces that the object supports.
35
+ attr_reader :intfs
36
+ # The service that the object is exported by.
37
+ attr_writer :service
38
+
39
+ @@intfs = Hash.new
40
+ @@cur_intf = nil
41
+ @@intfs_mutex = Mutex.new
42
+
43
+ # Create a new object with a given _path_.
44
+ def initialize(path)
45
+ @path = path
46
+ @intfs = @@intfs.dup
47
+ @service = nil
48
+ end
49
+
50
+ # State that the object implements the given _intf_.
51
+ def implements(intf)
52
+ @intfs[intf.name] = intf
53
+ end
54
+
55
+ # Dispatch a message _msg_.
56
+ def dispatch(msg)
57
+ case msg.message_type
58
+ when Message::METHOD_CALL
59
+ if not @intfs[msg.interface]
60
+ raise InterfaceNotInObject, msg.interface
61
+ end
62
+ meth = @intfs[msg.interface].methods[msg.member.to_sym]
63
+ raise MethodNotInInterface if not meth
64
+ methname = Object.make_method_name(msg.interface, msg.member)
65
+ reply = nil
66
+ begin
67
+ retdata = method(methname).call(*msg.params)
68
+ retdata = [*retdata]
69
+
70
+ reply = Message.new.reply_to(msg)
71
+ meth.rets.zip(retdata).each do |rsig, rdata|
72
+ reply.add_param(rsig.type, rdata)
73
+ end
74
+ rescue => ex
75
+ puts("DBus call Error: #{ex.to_s}")
76
+ reply = Message.error(msg, "org.freedesktop.DBus.Error.Failed", "#{ex.class}: #{ex}\n==== Backtrace ====\n#{ex.backtrace.join("\n")}")
77
+ end
78
+ @service.bus.send(reply.marshall)
79
+ end
80
+ end
81
+
82
+ # Select (and create) the interface that the following defined methods
83
+ # belong to.
84
+ def self.dbus_interface(s)
85
+ @@intfs_mutex.synchronize do
86
+ @@cur_intf = @@intfs[s] = Interface.new(s)
87
+ yield
88
+ @@cur_intf = nil
89
+ end
90
+ end
91
+
92
+ # Dummy undefined interface class.
93
+ class UndefinedInterface < ScriptError
94
+ def initialize(sym)
95
+ super "No interface specified for #{sym}"
96
+ end
97
+ end
98
+
99
+ # Defines an exportable method on the object with the given name _sym_,
100
+ # _prototype_ and the code in a block.
101
+ def self.dbus_method(sym, protoype = "", &block)
102
+ raise UndefinedInterface, sym if @@cur_intf.nil?
103
+ @@cur_intf.define(Method.new(sym.to_s).from_prototype(protoype))
104
+ define_method(Object.make_method_name(@@cur_intf.name, sym.to_s), &block)
105
+ end
106
+
107
+ # Emits a signal from the object with the given _interface_, signal
108
+ # _sig_ and arguments _args_.
109
+ def emit(intf, sig, *args)
110
+ @service.bus.emit(@service, self, intf, sig, *args)
111
+ end
112
+
113
+ # Defines a signal for the object with a given name _sym_ and _prototype_.
114
+ def self.dbus_signal(sym, protoype = "")
115
+ raise UndefinedInterface, sym if @@cur_intf.nil?
116
+ cur_intf = @@cur_intf
117
+ signal = Signal.new(sym.to_s).from_prototype(protoype)
118
+ cur_intf.define(Signal.new(sym.to_s).from_prototype(protoype))
119
+ define_method(sym.to_s) do |*args|
120
+ emit(cur_intf, signal, *args)
121
+ end
122
+ end
123
+
124
+ ####################################################################
125
+ private
126
+
127
+ # Helper method that returns a method name generated from the interface
128
+ # name _intfname_ and method name _methname_.
129
+ def self.make_method_name(intfname, methname)
130
+ "#{intfname}%%#{methname}"
131
+ end
132
+ end # class Object
133
+ end # module DBus
@@ -0,0 +1,544 @@
1
+ # dbus/introspection.rb - module containing a low-level D-Bus introspection implementation
2
+ #
3
+ # This file is part of the ruby-dbus project
4
+ # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
5
+ #
6
+ # This library is free software; you can redistribute it and/or
7
+ # modify it under the terms of the GNU Lesser General Public
8
+ # License, version 2.1 as published by the Free Software Foundation.
9
+ # See the file "COPYING" for the exact licensing terms.
10
+
11
+ require 'rexml/document'
12
+
13
+ module DBus
14
+ # Regular expressions that should match all method names.
15
+ MethodSignalRE = /^[A-Za-z][A-Za-z0-9_]*$/
16
+ # Regular expressions that should match all interface names.
17
+ InterfaceElementRE = /^[A-Za-z][A-Za-z0-9_]*$/
18
+
19
+ # Exception raised when an unknown signal is used.
20
+ class UnknownSignal < Exception
21
+ end
22
+
23
+ # Exception raised when an invalid class definition is encountered.
24
+ class InvalidClassDefinition < Exception
25
+ end
26
+
27
+ # = D-Bus interface class
28
+ #
29
+ # This class is the interface descriptor. In most cases, the Introspect()
30
+ # method call instanciates and configures this class for us.
31
+ #
32
+ # It also is the local definition of interface exported by the program.
33
+ class Interface
34
+ # The name of the interface.
35
+ attr_reader :name
36
+ # The methods that are part of the interface.
37
+ attr_reader :methods
38
+ # The signals that are part of the interface.
39
+ attr_reader :signals
40
+
41
+ # Creates a new interface with a given _name_.
42
+ def initialize(name)
43
+ validate_name(name)
44
+ @name = name
45
+ @methods, @signals = Hash.new, Hash.new
46
+ end
47
+
48
+ # Validates a service _name_.
49
+ def validate_name(name)
50
+ raise InvalidIntrospectionData if name.size > 255
51
+ raise InvalidIntrospectionData if name =~ /^\./ or name =~ /\.$/
52
+ raise InvalidIntrospectionData if name =~ /\.\./
53
+ raise InvalidIntrospectionData if not name =~ /\./
54
+ name.split(".").each do |element|
55
+ raise InvalidIntrospectionData if not element =~ InterfaceElementRE
56
+ end
57
+ end
58
+
59
+ # Helper method for defining a method _m_.
60
+ def define(m)
61
+ if m.kind_of?(Method)
62
+ @methods[m.name.to_sym] = m
63
+ elsif m.kind_of?(Signal)
64
+ @signals[m.name.to_sym] = m
65
+ end
66
+ end
67
+ alias :<< :define
68
+
69
+ # Defines a method with name _id_ and a given _prototype_ in the
70
+ # interface.
71
+ def define_method(id, prototype)
72
+ m = Method.new(id)
73
+ m.from_prototype(prototype)
74
+ define(m)
75
+ end
76
+ end # class Interface
77
+
78
+ # = A formal parameter has a name and a type
79
+ class FormalParameter
80
+ attr_reader :name
81
+ attr_reader :type
82
+
83
+ def initialize(name, type)
84
+ @name = name
85
+ @type = type
86
+ end
87
+
88
+ # backward compatibility, deprecated
89
+ def [](index)
90
+ case index
91
+ when 0: name
92
+ when 1: type
93
+ else nil
94
+ end
95
+ end
96
+ end
97
+
98
+ # = D-Bus interface element class
99
+ #
100
+ # This is a generic class for entities that are part of the interface
101
+ # such as methods and signals.
102
+ class InterfaceElement
103
+ # The name of the interface element.
104
+ attr_reader :name
105
+ # The parameters of the interface element
106
+ attr_reader :params
107
+
108
+ # Validates element _name_.
109
+ def validate_name(name)
110
+ if (not name =~ MethodSignalRE) or (name.size > 255)
111
+ raise InvalidMethodName name
112
+ end
113
+ end
114
+
115
+ # Creates a new element with the given _name_.
116
+ def initialize(name)
117
+ validate_name(name.to_s)
118
+ @name = name
119
+ @params = Array.new
120
+ end
121
+
122
+ # Adds a formal parameter with _name_ and _signature_
123
+ # (See also Message#add_param which takes signature+value)
124
+ def add_fparam(name, signature)
125
+ @params << FormalParameter.new(name, signature)
126
+ end
127
+ end # class InterfaceElement
128
+
129
+ # = D-Bus interface method class
130
+ #
131
+ # This is a class representing methods that are part of an interface.
132
+ class Method < InterfaceElement
133
+ # The list of return values for the method.
134
+ attr_reader :rets
135
+
136
+ # Creates a new method interface element with the given _name_.
137
+ def initialize(name)
138
+ super(name)
139
+ @rets = Array.new
140
+ end
141
+
142
+ # Add a return value _name_ and _signature_.
143
+ def add_return(name, signature)
144
+ @rets << FormalParameter.new(name, signature)
145
+ end
146
+
147
+ # Add parameter types by parsing the given _prototype_.
148
+ def from_prototype(prototype)
149
+ prototype.split(/, */).each do |arg|
150
+ arg = arg.split(" ")
151
+ raise InvalidClassDefinition if arg.size != 2
152
+ dir, arg = arg
153
+ if arg =~ /:/
154
+ arg = arg.split(":")
155
+ name, sig = arg
156
+ else
157
+ sig = arg
158
+ end
159
+ case dir
160
+ when "in"
161
+ add_fparam(name, sig)
162
+ when "out"
163
+ add_return(name, sig)
164
+ end
165
+ end
166
+ self
167
+ end
168
+
169
+ # Return an XML string representation of the method interface elment.
170
+ def to_xml
171
+ xml = %{<method name="#{@name}">\n}
172
+ @params.each do |param|
173
+ name = param.name ? %{name="#{param.name}" } : ""
174
+ xml += %{<arg #{name}direction="in" type="#{param.type}"/>\n}
175
+ end
176
+ @rets.each do |param|
177
+ name = param.name ? %{name="#{param.name}" } : ""
178
+ xml += %{<arg #{name}direction="out" type="#{param.type}"/>\n}
179
+ end
180
+ xml += %{</method>\n}
181
+ xml
182
+ end
183
+ end # class Method
184
+
185
+ # = D-Bus interface signal class
186
+ #
187
+ # This is a class representing signals that are part of an interface.
188
+ class Signal < InterfaceElement
189
+ # Add parameter types based on the given _prototype_.
190
+ def from_prototype(prototype)
191
+ prototype.split(/, */).each do |arg|
192
+ if arg =~ /:/
193
+ arg = arg.split(":")
194
+ name, sig = arg
195
+ else
196
+ sig = arg
197
+ end
198
+ add_fparam(name, sig)
199
+ end
200
+ self
201
+ end
202
+
203
+ # Return an XML string representation of the signal interface elment.
204
+ def to_xml
205
+ xml = %{<signal name="#{@name}">\n}
206
+ @params.each do |param|
207
+ name = param.name ? %{name="#{param.name}" } : ""
208
+ xml += %{<arg #{name}type="#{param.type}"/>\n}
209
+ end
210
+ xml += %{</signal>\n}
211
+ xml
212
+ end
213
+ end # class Signal
214
+
215
+ # = D-Bus introspect XML parser class
216
+ #
217
+ # This class parses introspection XML of an object and constructs a tree
218
+ # of Node, Interface, Method, Signal instances.
219
+ class IntrospectXMLParser
220
+ # Creates a new parser for XML data in string _xml_.
221
+ def initialize(xml)
222
+ @xml = xml
223
+ end
224
+
225
+ # Recursively parses the subnodes, constructing the tree.
226
+ def parse_subnodes
227
+ subnodes = Array.new
228
+ t = Time.now
229
+ d = REXML::Document.new(@xml)
230
+ d.elements.each("node/node") do |e|
231
+ subnodes << e.attributes["name"]
232
+ end
233
+ subnodes
234
+ end
235
+
236
+ # Parses the XML, constructing the tree.
237
+ def parse
238
+ ret = Array.new
239
+ subnodes = Array.new
240
+ t = Time.now
241
+ d = REXML::Document.new(@xml)
242
+ d.elements.each("node/node") do |e|
243
+ subnodes << e.attributes["name"]
244
+ end
245
+ d.elements.each("node/interface") do |e|
246
+ i = Interface.new(e.attributes["name"])
247
+ e.elements.each("method") do |me|
248
+ m = Method.new(me.attributes["name"])
249
+ parse_methsig(me, m)
250
+ i << m
251
+ end
252
+ e.elements.each("signal") do |se|
253
+ s = Signal.new(se.attributes["name"])
254
+ parse_methsig(se, s)
255
+ i << s
256
+ end
257
+ ret << i
258
+ end
259
+ d = Time.now - t
260
+ if d > 2
261
+ puts "Some XML took more that two secs to parse. Optimize me!" if $DEBUG
262
+ end
263
+ [ret, subnodes]
264
+ end
265
+
266
+ ######################################################################
267
+ private
268
+
269
+ # Parses a method signature XML element _e_ and initialises
270
+ # method/signal _m_.
271
+ def parse_methsig(e, m)
272
+ e.elements.each("arg") do |ae|
273
+ name = ae.attributes["name"]
274
+ dir = ae.attributes["direction"]
275
+ sig = ae.attributes["type"]
276
+ if m.is_a?(DBus::Signal)
277
+ m.add_fparam(name, sig)
278
+ elsif m.is_a?(DBus::Method)
279
+ case dir
280
+ when "in"
281
+ m.add_fparam(name, sig)
282
+ when "out"
283
+ m.add_return(name, sig)
284
+ end
285
+ else
286
+ raise NotImplementedError, dir
287
+ end
288
+ end
289
+ end
290
+ end # class IntrospectXMLParser
291
+
292
+ # = D-Bus proxy object interface class
293
+ #
294
+ # A class similar to the normal Interface used as a proxy for remote
295
+ # object interfaces.
296
+ class ProxyObjectInterface
297
+ # The proxied methods contained in the interface.
298
+ attr_accessor :methods
299
+ # The proxied signals contained in the interface.
300
+ attr_accessor :signals
301
+ # The proxy object to which this interface belongs.
302
+ attr_reader :object
303
+ # The name of the interface.
304
+ attr_reader :name
305
+
306
+ # Creates a new proxy interface for the given proxy _object_
307
+ # and the given _name_.
308
+ def initialize(object, name)
309
+ @object, @name = object, name
310
+ @methods, @signals = Hash.new, Hash.new
311
+ end
312
+
313
+ # Returns the string representation of the interface (the name).
314
+ def to_str
315
+ @name
316
+ end
317
+
318
+ # Returns the singleton class of the interface.
319
+ def singleton_class
320
+ (class << self ; self ; end)
321
+ end
322
+
323
+ # FIXME
324
+ def check_for_eval(s)
325
+ raise RuntimeError, "invalid internal data '#{s}'" if not s.to_s =~ /^[A-Za-z0-9_]*$/
326
+ end
327
+
328
+ # FIXME
329
+ def check_for_quoted_eval(s)
330
+ raise RuntimeError, "invalid internal data '#{s}'" if not s.to_s =~ /^[^"]+$/
331
+ end
332
+
333
+ # Defines a method on the interface from the Method descriptor _m_.
334
+ def define_method_from_descriptor(m)
335
+ check_for_eval(m.name)
336
+ check_for_quoted_eval(@name)
337
+ methdef = "def #{m.name}("
338
+ methdef += (0..(m.params.size - 1)).to_a.collect { |n|
339
+ "arg#{n}"
340
+ }.join(", ")
341
+ methdef += %{)
342
+ msg = Message.new(Message::METHOD_CALL)
343
+ msg.path = @object.path
344
+ msg.interface = "#{@name}"
345
+ msg.destination = @object.destination
346
+ msg.member = "#{m.name}"
347
+ msg.sender = @object.bus.unique_name
348
+ }
349
+ idx = 0
350
+ m.params.each do |fpar|
351
+ par = fpar.type
352
+ check_for_quoted_eval(par)
353
+
354
+ # This is the signature validity check
355
+ Type::Parser.new(par).parse
356
+
357
+ methdef += %{
358
+ msg.add_param("#{par}", arg#{idx})
359
+ }
360
+ idx += 1
361
+ end
362
+ methdef += "
363
+ ret = nil
364
+ if block_given?
365
+ @object.bus.on_return(msg) do |rmsg|
366
+ if rmsg.is_a?(Error)
367
+ yield(rmsg)
368
+ else
369
+ yield(rmsg, *rmsg.params)
370
+ end
371
+ end
372
+ @object.bus.send(msg.marshall)
373
+ else
374
+ @object.bus.send_sync(msg) do |rmsg|
375
+ if rmsg.is_a?(Error)
376
+ raise rmsg
377
+ else
378
+ ret = rmsg.params
379
+ end
380
+ end
381
+ end
382
+ ret
383
+ end
384
+ "
385
+ singleton_class.class_eval(methdef)
386
+ @methods[m.name] = m
387
+ end
388
+
389
+ # Defines a signal from the descriptor _s_.
390
+ def define_signal_from_descriptor(s)
391
+ @signals[s.name] = s
392
+ end
393
+
394
+ # Defines a signal or method based on the descriptor _m_.
395
+ def define(m)
396
+ if m.kind_of?(Method)
397
+ define_method_from_descriptor(m)
398
+ elsif m.kind_of?(Signal)
399
+ define_signal_from_descriptor(m)
400
+ end
401
+ end
402
+
403
+ # Defines a proxied method on the interface.
404
+ def define_method(methodname, prototype)
405
+ m = Method.new(methodname)
406
+ m.from_prototype(prototype)
407
+ define(m)
408
+ end
409
+
410
+ # Registers a handler (code block) for a signal with _name_ arriving
411
+ # over the given _bus_.
412
+ def on_signal(bus, name, &block)
413
+ mr = DBus::MatchRule.new.from_signal(self, name)
414
+ bus.add_match(mr) { |msg| block.call(*msg.params) }
415
+ end
416
+ end # class ProxyObjectInterface
417
+
418
+ # D-Bus proxy object class
419
+ #
420
+ # Class representing a remote object in an external application.
421
+ # Typically, calling a method on an instance of a ProxyObject sends a message
422
+ # over the bus so that the method is executed remotely on the correctponding
423
+ # object.
424
+ class ProxyObject
425
+ # The subnodes of the object in the tree.
426
+ attr_accessor :subnodes
427
+ # Flag determining whether the object has been introspected.
428
+ attr_accessor :introspected
429
+ # The (remote) destination of the object.
430
+ attr_reader :destination
431
+ # The path to the object.
432
+ attr_reader :path
433
+ # The bus the object is reachable via.
434
+ attr_reader :bus
435
+ # The default interface of the object.
436
+ attr_accessor :default_iface
437
+
438
+ # Creates a new proxy object living on the given _bus_ at destination _dest_
439
+ # on the given _path_.
440
+ def initialize(bus, dest, path)
441
+ @bus, @destination, @path = bus, dest, path
442
+ @interfaces = Hash.new
443
+ @subnodes = Array.new
444
+ end
445
+
446
+ # Returns the interfaces of the object.
447
+ def interfaces
448
+ @interfaces.keys
449
+ end
450
+
451
+ # Retrieves an interface of the proxy object (ProxyObjectInterface instance).
452
+ def [](intfname)
453
+ @interfaces[intfname]
454
+ end
455
+
456
+ # Maps the given interface name _intfname_ to the given interface _intf.
457
+ def []=(intfname, intf)
458
+ @interfaces[intfname] = intf
459
+ end
460
+
461
+ # Introspects the remote object. Allows you to find and select
462
+ # interfaces on the object.
463
+ def introspect
464
+ # Synchronous call here.
465
+ xml = @bus.introspect_data(@destination, @path)
466
+ ProxyObjectFactory.introspect_into(self, xml)
467
+ xml
468
+ end
469
+
470
+ # Returns whether the object has an interface with the given _name_.
471
+ def has_iface?(name)
472
+ raise "Cannot call has_iface? is not introspected" if not @introspected
473
+ @interfaces.member?(name)
474
+ end
475
+
476
+ # Registers a handler, the code block, for a signal with the given _name_.
477
+ def on_signal(name, &block)
478
+ if @default_iface and has_iface?(@default_iface)
479
+ @interfaces[@default_iface].on_signal(@bus, name, &block)
480
+ else
481
+ # TODO improve
482
+ raise NoMethodError
483
+ end
484
+ end
485
+
486
+ ####################################################
487
+ private
488
+
489
+ # Handles all unkown methods, mostly to route method calls to the
490
+ # default interface.
491
+ def method_missing(name, *args)
492
+ if @default_iface and has_iface?(@default_iface)
493
+ begin
494
+ @interfaces[@default_iface].method(name).call(*args)
495
+ rescue NameError => e
496
+ # interesting, foo.method("unknown")
497
+ # raises NameError, not NoMethodError
498
+ match = /undefined method `([^']*)' for class `([^']*)'/.match e
499
+ raise unless match and match[2] == "DBus::ProxyObjectInterface"
500
+ # BTW e.exception("...") would preserve the class.
501
+ raise NoMethodError,"undefined method `#{match[1]}' for DBus interface `#{@default_iface}' on object `#{@path}'"
502
+ end
503
+ else
504
+ # TODO distinguish:
505
+ # - di not specified
506
+ #TODO
507
+ # - di is specified but not found in introspection data
508
+ raise NoMethodError, "undefined method `#{name}' for DBus interface `#{@default_iface}' on object `#{@path}'"
509
+ end
510
+ end
511
+ end # class ProxyObject
512
+
513
+ # = D-Bus proxy object factory class
514
+ #
515
+ # Class that generates and sets up a proxy object based on introspection data.
516
+ class ProxyObjectFactory
517
+ # Creates a new proxy object factory for the given introspection XML _xml_,
518
+ # _bus_, destination _dest_, and _path_.
519
+ def initialize(xml, bus, dest, path)
520
+ @xml, @bus, @path, @dest = xml, bus, path, dest
521
+ end
522
+
523
+ # Investigates the sub-nodes of the proxy object _po_ based on the
524
+ # introspection XML data _xml_ and sets them up recursively.
525
+ def ProxyObjectFactory.introspect_into(po, xml)
526
+ intfs, po.subnodes = IntrospectXMLParser.new(xml).parse
527
+ intfs.each do |i|
528
+ poi = ProxyObjectInterface.new(po, i.name)
529
+ i.methods.each_value { |m| poi.define(m) }
530
+ i.signals.each_value { |s| poi.define(s) }
531
+ po[i.name] = poi
532
+ end
533
+ po.introspected = true
534
+ end
535
+
536
+ # Generates, sets up and returns the proxy object.
537
+ def build
538
+ po = ProxyObject.new(@bus, @dest, @path)
539
+ ProxyObjectFactory.introspect_into(po, @xml)
540
+ po
541
+ end
542
+ end # class ProxyObjectFactory
543
+ end # module DBus
544
+