ruby-dbus 0.10.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/NEWS +8 -0
  3. data/README.md +41 -4
  4. data/Rakefile +15 -16
  5. data/VERSION +1 -1
  6. data/lib/dbus.rb +18 -14
  7. data/lib/dbus/bus.rb +32 -168
  8. data/lib/dbus/error.rb +4 -2
  9. data/lib/dbus/export.rb +3 -2
  10. data/lib/dbus/introspect.rb +0 -294
  11. data/lib/dbus/marshall.rb +1 -1
  12. data/lib/dbus/message.rb +8 -0
  13. data/lib/dbus/message_queue.rb +166 -0
  14. data/lib/dbus/proxy_object.rb +149 -0
  15. data/lib/dbus/proxy_object_factory.rb +41 -0
  16. data/lib/dbus/proxy_object_interface.rb +128 -0
  17. data/ruby-dbus.gemspec +1 -0
  18. data/test/{async_test.rb → async_spec.rb} +10 -11
  19. data/test/{binding_test.rb → binding_spec.rb} +23 -25
  20. data/test/bus_and_xml_backend_spec.rb +39 -0
  21. data/test/bus_driver_spec.rb +20 -0
  22. data/test/{bus_test.rb → bus_spec.rb} +8 -7
  23. data/test/byte_array_spec.rb +38 -0
  24. data/test/err_msg_spec.rb +42 -0
  25. data/test/{introspect_xml_parser_test.rb → introspect_xml_parser_spec.rb} +5 -6
  26. data/test/introspection_spec.rb +32 -0
  27. data/test/{main_loop_test.rb → main_loop_spec.rb} +10 -7
  28. data/test/property_spec.rb +53 -0
  29. data/test/server_robustness_spec.rb +66 -0
  30. data/test/{server_test.rb → server_spec.rb} +10 -11
  31. data/test/service_newapi.rb +1 -1
  32. data/test/session_bus_spec_manual.rb +15 -0
  33. data/test/{signal_test.rb → signal_spec.rb} +13 -14
  34. data/test/spec_helper.rb +33 -0
  35. data/test/{thread_safety_test.rb → thread_safety_spec.rb} +5 -6
  36. data/test/type_spec.rb +19 -0
  37. data/test/{value_test.rb → value_spec.rb} +23 -24
  38. data/test/variant_spec.rb +66 -0
  39. metadata +40 -22
  40. data/test/bus_and_xml_backend_test.rb +0 -43
  41. data/test/bus_driver_test.rb +0 -19
  42. data/test/byte_array_test.rb +0 -42
  43. data/test/err_msg_test.rb +0 -59
  44. data/test/introspection_test.rb +0 -32
  45. data/test/property_test.rb +0 -65
  46. data/test/server_robustness_test.rb +0 -73
  47. data/test/session_bus_test_manual.rb +0 -17
  48. data/test/test_helper.rb +0 -14
  49. data/test/type_test.rb +0 -23
  50. data/test/variant_test.rb +0 -67
@@ -34,8 +34,10 @@ module DBus
34
34
  end
35
35
  end # class Error
36
36
 
37
- # raise DBus.error, "message"
38
- # raise DBus.error("org.example.Error.SeatOccupied"), "Seat #{n} is occupied"
37
+ # @example raise a generic error
38
+ # raise DBus.error, "message"
39
+ # @example raise a specific error
40
+ # raise DBus.error("org.example.Error.SeatOccupied"), "Seat #{n} is occupied"
39
41
  def error(name = "org.freedesktop.DBus.Error.Failed")
40
42
  # message will be set by Kernel.raise
41
43
  DBus::Error.new(nil, name)
@@ -64,9 +64,10 @@ module DBus
64
64
  reply.add_param(rsig.type, rdata)
65
65
  end
66
66
  rescue => ex
67
- reply = ErrorMessage.from_exception(ex).reply_to(msg)
67
+ dbus_msg_exc = msg.annotate_exception(ex)
68
+ reply = ErrorMessage.from_exception(dbus_msg_exc).reply_to(msg)
68
69
  end
69
- @service.bus.send(reply.marshall)
70
+ @service.bus.message_queue.push(reply)
70
71
  end
71
72
  end
72
73
 
@@ -215,299 +215,5 @@ module DBus
215
215
  xml
216
216
  end
217
217
  end # class Signal
218
-
219
- # = D-Bus proxy object interface class
220
- #
221
- # A class similar to the normal Interface used as a proxy for remote
222
- # object interfaces.
223
- class ProxyObjectInterface
224
- # The proxied methods contained in the interface.
225
- attr_accessor :methods
226
- # The proxied signals contained in the interface.
227
- attr_accessor :signals
228
- # The proxy object to which this interface belongs.
229
- attr_reader :object
230
- # The name of the interface.
231
- attr_reader :name
232
-
233
- # Creates a new proxy interface for the given proxy _object_
234
- # and the given _name_.
235
- def initialize(object, name)
236
- @object, @name = object, name
237
- @methods, @signals = Hash.new, Hash.new
238
- end
239
-
240
- # Returns the string representation of the interface (the name).
241
- def to_str
242
- @name
243
- end
244
-
245
- # Returns the singleton class of the interface.
246
- def singleton_class
247
- (class << self ; self ; end)
248
- end
249
-
250
- # Defines a method on the interface from the Method descriptor _m_.
251
- def define_method_from_descriptor(m)
252
- m.params.each do |fpar|
253
- par = fpar.type
254
- # This is the signature validity check
255
- Type::Parser.new(par).parse
256
- end
257
-
258
- singleton_class.class_eval do
259
- define_method m.name do |*args, &reply_handler|
260
- if m.params.size != args.size
261
- raise ArgumentError, "wrong number of arguments (#{args.size} for #{m.params.size})"
262
- end
263
-
264
- msg = Message.new(Message::METHOD_CALL)
265
- msg.path = @object.path
266
- msg.interface = @name
267
- msg.destination = @object.destination
268
- msg.member = m.name
269
- msg.sender = @object.bus.unique_name
270
- m.params.each do |fpar|
271
- par = fpar.type
272
- msg.add_param(par, args.shift)
273
- end
274
- @object.bus.send_sync_or_async(msg, &reply_handler)
275
- end
276
- end
277
-
278
- @methods[m.name] = m
279
- end
280
-
281
- # Defines a signal from the descriptor _s_.
282
- def define_signal_from_descriptor(s)
283
- @signals[s.name] = s
284
- end
285
-
286
- # Defines a signal or method based on the descriptor _m_.
287
- def define(m)
288
- if m.kind_of?(Method)
289
- define_method_from_descriptor(m)
290
- elsif m.kind_of?(Signal)
291
- define_signal_from_descriptor(m)
292
- end
293
- end
294
-
295
- # Defines a proxied method on the interface.
296
- def define_method(methodname, prototype)
297
- m = Method.new(methodname)
298
- m.from_prototype(prototype)
299
- define(m)
300
- end
301
-
302
- # @overload on_signal(name, &block)
303
- # @overload on_signal(bus, name, &block)
304
- # Registers a handler (code block) for a signal with _name_ arriving
305
- # over the given _bus_. If no block is given, the signal is unregistered.
306
- # Note that specifying _bus_ is discouraged and the option is kept only for
307
- # backward compatibility.
308
- # @return [void]
309
- def on_signal(bus = @object.bus, name, &block)
310
- mr = DBus::MatchRule.new.from_signal(self, name)
311
- if block.nil?
312
- bus.remove_match(mr)
313
- else
314
- bus.add_match(mr) { |msg| block.call(*msg.params) }
315
- end
316
- end
317
-
318
- PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties"
319
-
320
- # Read a property.
321
- def [](propname)
322
- self.object[PROPERTY_INTERFACE].Get(self.name, propname)[0]
323
- end
324
-
325
- # Write a property.
326
- def []=(propname, value)
327
- self.object[PROPERTY_INTERFACE].Set(self.name, propname, value)
328
- end
329
-
330
- # Read all properties at once, as a hash.
331
- # @return [Hash{String}]
332
- def all_properties
333
- self.object[PROPERTY_INTERFACE].GetAll(self.name)[0]
334
- end
335
- end # class ProxyObjectInterface
336
-
337
- # D-Bus proxy object class
338
- #
339
- # Class representing a remote object in an external application.
340
- # Typically, calling a method on an instance of a ProxyObject sends a message
341
- # over the bus so that the method is executed remotely on the correctponding
342
- # object.
343
- class ProxyObject
344
- # The names of direct subnodes of the object in the tree.
345
- attr_accessor :subnodes
346
- # Flag determining whether the object has been introspected.
347
- attr_accessor :introspected
348
- # The (remote) destination of the object.
349
- attr_reader :destination
350
- # The path to the object.
351
- attr_reader :path
352
- # The bus the object is reachable via.
353
- attr_reader :bus
354
- # @return [String] The name of the default interface of the object.
355
- attr_accessor :default_iface
356
-
357
- # Creates a new proxy object living on the given _bus_ at destination _dest_
358
- # on the given _path_.
359
- def initialize(bus, dest, path)
360
- @bus, @destination, @path = bus, dest, path
361
- @interfaces = Hash.new
362
- @subnodes = Array.new
363
- end
364
-
365
- # Returns the interfaces of the object.
366
- def interfaces
367
- @interfaces.keys
368
- end
369
-
370
- # Retrieves an interface of the proxy object
371
- # @param [String] intfname
372
- # @return [ProxyObjectInterface]
373
- def [](intfname)
374
- @interfaces[intfname]
375
- end
376
-
377
- # Maps the given interface name _intfname_ to the given interface _intf.
378
- # @param [String] intfname
379
- # @param [ProxyObjectInterface] intf
380
- # @return [ProxyObjectInterface]
381
- def []=(intfname, intf)
382
- @interfaces[intfname] = intf
383
- end
384
-
385
- # Introspects the remote object. Allows you to find and select
386
- # interfaces on the object.
387
- def introspect
388
- # Synchronous call here.
389
- xml = @bus.introspect_data(@destination, @path)
390
- ProxyObjectFactory.introspect_into(self, xml)
391
- define_shortcut_methods()
392
- xml
393
- end
394
-
395
- # For each non duplicated method name in any interface present on the
396
- # caller, defines a shortcut method dynamically.
397
- # This function is automatically called when a {ProxyObject} is
398
- # introspected.
399
- def define_shortcut_methods
400
- # builds a list of duplicated methods
401
- dup_meths, univocal_meths = [],{}
402
- @interfaces.each_value do |intf|
403
- intf.methods.each_value do |meth|
404
- # Module#instance_methods give us an array of symbols or strings,
405
- # depending on which version
406
- name = if RUBY_VERSION >= "1.9"
407
- meth.name.to_sym
408
- else
409
- meth.name
410
- end
411
- # don't overwrite instance methods!
412
- if dup_meths.include? name or self.class.instance_methods.include? name
413
- next
414
- elsif univocal_meths.include? name
415
- univocal_meths.delete name
416
- dup_meths << name
417
- else
418
- univocal_meths[name] = intf
419
- end
420
- end
421
- end
422
- univocal_meths.each do |name, intf|
423
- # creates a shortcut function that forwards each call to the method on
424
- # the appropriate intf
425
- singleton_class.class_eval do
426
- define_method name do |*args, &reply_handler|
427
- intf.method(name).call(*args, &reply_handler)
428
- end
429
- end
430
- end
431
- end
432
-
433
- # Returns whether the object has an interface with the given _name_.
434
- def has_iface?(name)
435
- raise "Cannot call has_iface? if not introspected" if not @introspected
436
- @interfaces.member?(name)
437
- end
438
-
439
- # Registers a handler, the code block, for a signal with the given _name_.
440
- # It uses _default_iface_ which must have been set.
441
- # @return [void]
442
- def on_signal(name, &block)
443
- if @default_iface and has_iface?(@default_iface)
444
- @interfaces[@default_iface].on_signal(name, &block)
445
- else
446
- # TODO improve
447
- raise NoMethodError
448
- end
449
- end
450
-
451
- ####################################################
452
- private
453
-
454
- # Handles all unkown methods, mostly to route method calls to the
455
- # default interface.
456
- def method_missing(name, *args, &reply_handler)
457
- if @default_iface and has_iface?(@default_iface)
458
- begin
459
- @interfaces[@default_iface].method(name).call(*args, &reply_handler)
460
- rescue NameError => e
461
- # interesting, foo.method("unknown")
462
- # raises NameError, not NoMethodError
463
- raise unless e.to_s =~ /undefined method `#{name}'/
464
- # BTW e.exception("...") would preserve the class.
465
- raise NoMethodError,"undefined method `#{name}' for DBus interface `#{@default_iface}' on object `#{@path}'"
466
- end
467
- else
468
- # TODO distinguish:
469
- # - di not specified
470
- #TODO
471
- # - di is specified but not found in introspection data
472
- raise NoMethodError, "undefined method `#{name}' for DBus interface `#{@default_iface}' on object `#{@path}'"
473
- end
474
- end
475
-
476
- # Returns the singleton class of the object.
477
- def singleton_class
478
- (class << self ; self ; end)
479
- end
480
- end # class ProxyObject
481
-
482
- # = D-Bus proxy object factory class
483
- #
484
- # Class that generates and sets up a proxy object based on introspection data.
485
- class ProxyObjectFactory
486
- # Creates a new proxy object factory for the given introspection XML _xml_,
487
- # _bus_, destination _dest_, and _path_.
488
- def initialize(xml, bus, dest, path)
489
- @xml, @bus, @path, @dest = xml, bus, path, dest
490
- end
491
-
492
- # Investigates the sub-nodes of the proxy object _po_ based on the
493
- # introspection XML data _xml_ and sets them up recursively.
494
- def ProxyObjectFactory.introspect_into(po, xml)
495
- intfs, po.subnodes = IntrospectXMLParser.new(xml).parse
496
- intfs.each do |i|
497
- poi = ProxyObjectInterface.new(po, i.name)
498
- i.methods.each_value { |m| poi.define(m) }
499
- i.signals.each_value { |s| poi.define(s) }
500
- po[i.name] = poi
501
- end
502
- po.introspected = true
503
- end
504
-
505
- # Generates, sets up and returns the proxy object.
506
- def build
507
- po = ProxyObject.new(@bus, @dest, @path)
508
- ProxyObjectFactory.introspect_into(po, @xml)
509
- po
510
- end
511
- end # class ProxyObjectFactory
512
218
  end # module DBus
513
219
 
@@ -212,7 +212,7 @@ module DBus
212
212
  packet = get_string
213
213
  when Type::STRING
214
214
  packet = get_string
215
- packet.force_encoding('UTF-8') if RUBY_VERSION >= '1.9'
215
+ packet.force_encoding('UTF-8')
216
216
  when Type::SIGNATURE
217
217
  packet = get_signature
218
218
  when Type::DICT_ENTRY
@@ -235,6 +235,14 @@ module DBus
235
235
  ret, _ = unmarshall_buffer(buf)
236
236
  ret
237
237
  end
238
+
239
+ # Make a new exception from ex, mark it as being caused by this message
240
+ # @api private
241
+ def annotate_exception(ex)
242
+ new_ex = ex.exception("#{ex}; caused by #{self}")
243
+ new_ex.set_backtrace(ex.backtrace)
244
+ new_ex
245
+ end
238
246
  end # class Message
239
247
 
240
248
  class MethodReturnMessage < Message
@@ -0,0 +1,166 @@
1
+ # This file is part of the ruby-dbus project
2
+ # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
3
+ # Copyright (C) 2009-2014 Martin Vidner
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License, version 2.1 as published by the Free Software Foundation.
8
+ # See the file "COPYING" for the exact licensing terms.
9
+
10
+ require "fcntl"
11
+ require "socket"
12
+
13
+ module DBus
14
+ class MessageQueue
15
+ # The socket that is used to connect with the bus.
16
+ attr_reader :socket
17
+
18
+ def initialize(address)
19
+ @address = address
20
+ @buffer = ""
21
+ @is_tcp = false
22
+ connect
23
+ end
24
+
25
+ # TODO failure modes
26
+ #
27
+ # If _non_block_ is true, return nil instead of waiting
28
+ # EOFError may be raised
29
+ def pop(non_block = false)
30
+ buffer_from_socket_nonblock
31
+ message = message_from_buffer_nonblock
32
+ unless non_block
33
+ # we can block
34
+ while message.nil?
35
+ r, d, d = IO.select([@socket])
36
+ if r and r[0] == @socket
37
+ buffer_from_socket_nonblock
38
+ message = message_from_buffer_nonblock
39
+ end
40
+ end
41
+ end
42
+ message
43
+ end
44
+
45
+ def push(message)
46
+ @socket.write(message.marshall)
47
+ end
48
+ alias :<< :push
49
+
50
+ private
51
+
52
+ # Connect to the bus and initialize the connection.
53
+ def connect
54
+ addresses = @address.split ";"
55
+ # connect to first one that succeeds
56
+ worked = addresses.find do |a|
57
+ transport, keyvaluestring = a.split ":"
58
+ kv_list = keyvaluestring.split ","
59
+ kv_hash = Hash.new
60
+ kv_list.each do |kv|
61
+ key, escaped_value = kv.split "="
62
+ value = escaped_value.gsub(/%(..)/) {|m| [$1].pack "H2" }
63
+ kv_hash[key] = value
64
+ end
65
+ case transport
66
+ when "unix"
67
+ connect_to_unix kv_hash
68
+ when "tcp"
69
+ connect_to_tcp kv_hash
70
+ when "launchd"
71
+ connect_to_launchd kv_hash
72
+ else
73
+ # ignore, report?
74
+ end
75
+ end
76
+ worked
77
+ # returns the address that worked or nil.
78
+ # how to report failure?
79
+ end
80
+
81
+ # Connect to a bus over tcp and initialize the connection.
82
+ def connect_to_tcp(params)
83
+ #check if the path is sufficient
84
+ if params.key?("host") and params.key?("port")
85
+ begin
86
+ #initialize the tcp socket
87
+ @socket = TCPSocket.new(params["host"],params["port"].to_i)
88
+ @socket.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
89
+ init_connection
90
+ @is_tcp = true
91
+ rescue Exception => e
92
+ puts "Oops:", e
93
+ puts "Error: Could not establish connection to: #{@path}, will now exit."
94
+ exit(1) #a little harsh
95
+ end
96
+ else
97
+ #Danger, Will Robinson: the specified "path" is not usable
98
+ puts "Error: supplied path: #{@path}, unusable! sorry."
99
+ end
100
+ end
101
+
102
+ # Connect to an abstract unix bus and initialize the connection.
103
+ def connect_to_unix(params)
104
+ @socket = Socket.new(Socket::Constants::PF_UNIX,Socket::Constants::SOCK_STREAM, 0)
105
+ @socket.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
106
+ if ! params['abstract'].nil?
107
+ if HOST_END == LIL_END
108
+ sockaddr = "\1\0\0#{params['abstract']}"
109
+ else
110
+ sockaddr = "\0\1\0#{params['abstract']}"
111
+ end
112
+ elsif ! params['path'].nil?
113
+ sockaddr = Socket.pack_sockaddr_un(params['path'])
114
+ end
115
+ @socket.connect(sockaddr)
116
+ init_connection
117
+ end
118
+
119
+ def connect_to_launchd(params)
120
+ socket_var = params['env']
121
+ socket = `launchctl getenv #{socket_var}`.chomp
122
+ connect_to_unix 'path' => socket
123
+ end
124
+
125
+ # Initialize the connection to the bus.
126
+ def init_connection
127
+ @client = Client.new(@socket)
128
+ @client.authenticate
129
+ end
130
+
131
+ public # FIXME: fix Main loop instead
132
+
133
+ # Get and remove one message from the buffer.
134
+ # Return the message or nil.
135
+ def message_from_buffer_nonblock
136
+ return nil if @buffer.empty?
137
+ ret = nil
138
+ begin
139
+ ret, size = Message.new.unmarshall_buffer(@buffer)
140
+ @buffer.slice!(0, size)
141
+ rescue IncompleteBufferException
142
+ # fall through, let ret be null
143
+ end
144
+ ret
145
+ end
146
+
147
+ # The buffer size for messages.
148
+ MSG_BUF_SIZE = 4096
149
+
150
+ # Fill (append) the buffer from data that might be available on the
151
+ # socket.
152
+ # EOFError may be raised
153
+ def buffer_from_socket_nonblock
154
+ @buffer += @socket.read_nonblock(MSG_BUF_SIZE)
155
+ rescue EOFError
156
+ raise # the caller expects it
157
+ rescue Errno::EAGAIN
158
+ # fine, would block
159
+ rescue Exception => e
160
+ puts "Oops:", e
161
+ raise if @is_tcp # why?
162
+ puts "WARNING: read_nonblock failed, falling back to .recv"
163
+ @buffer += @socket.recv(MSG_BUF_SIZE)
164
+ end
165
+ end
166
+ end