ruby-dbus 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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