ruby-dbus 0.16.0 → 0.25.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 (108) hide show
  1. checksums.yaml +4 -4
  2. data/NEWS.md +288 -3
  3. data/README.md +3 -5
  4. data/Rakefile +27 -12
  5. data/VERSION +1 -1
  6. data/doc/Reference.md +109 -10
  7. data/examples/doc/_extract_examples +7 -0
  8. data/examples/no-bus/pulseaudio.rb +50 -0
  9. data/examples/no-introspect/nm-test.rb +2 -0
  10. data/examples/no-introspect/tracker-test.rb +3 -1
  11. data/examples/rhythmbox/playpause.rb +2 -1
  12. data/examples/service/call_service.rb +2 -1
  13. data/examples/service/complex-property.rb +21 -0
  14. data/examples/service/service_newapi.rb +3 -3
  15. data/examples/simple/call_introspect.rb +1 -0
  16. data/examples/simple/get_id.rb +6 -3
  17. data/examples/simple/properties.rb +2 -0
  18. data/examples/utils/listnames.rb +10 -1
  19. data/examples/utils/notify.rb +1 -0
  20. data/lib/dbus/api_options.rb +9 -0
  21. data/lib/dbus/auth.rb +307 -217
  22. data/lib/dbus/bus.rb +155 -588
  23. data/lib/dbus/bus_name.rb +12 -8
  24. data/lib/dbus/connection.rb +363 -0
  25. data/lib/dbus/core_ext/class/attribute.rb +1 -1
  26. data/lib/dbus/data.rb +821 -0
  27. data/lib/dbus/emits_changed_signal.rb +83 -0
  28. data/lib/dbus/error.rb +4 -2
  29. data/lib/dbus/introspect.rb +133 -31
  30. data/lib/dbus/logger.rb +6 -3
  31. data/lib/dbus/main.rb +66 -0
  32. data/lib/dbus/marshall.rb +247 -296
  33. data/lib/dbus/matchrule.rb +16 -16
  34. data/lib/dbus/message.rb +66 -57
  35. data/lib/dbus/message_queue.rb +40 -25
  36. data/lib/dbus/node_tree.rb +105 -0
  37. data/lib/dbus/object.rb +442 -33
  38. data/lib/dbus/object_manager.rb +61 -0
  39. data/lib/dbus/object_path.rb +11 -6
  40. data/lib/dbus/object_server.rb +149 -0
  41. data/lib/dbus/org.freedesktop.DBus.xml +97 -0
  42. data/lib/dbus/platform.rb +26 -0
  43. data/lib/dbus/proxy_object.rb +30 -11
  44. data/lib/dbus/proxy_object_factory.rb +13 -7
  45. data/lib/dbus/proxy_object_interface.rb +63 -30
  46. data/lib/dbus/proxy_service.rb +107 -0
  47. data/lib/dbus/raw_message.rb +91 -0
  48. data/lib/dbus/type.rb +319 -86
  49. data/lib/dbus/xml.rb +40 -19
  50. data/lib/dbus.rb +28 -17
  51. data/ruby-dbus.gemspec +13 -6
  52. data/spec/async_spec.rb +2 -0
  53. data/spec/auth_spec.rb +225 -0
  54. data/spec/binding_spec.rb +2 -0
  55. data/spec/bus_and_xml_backend_spec.rb +5 -1
  56. data/spec/bus_connection_spec.rb +93 -0
  57. data/spec/bus_name_spec.rb +3 -1
  58. data/spec/bus_spec.rb +2 -0
  59. data/spec/byte_array_spec.rb +2 -0
  60. data/spec/client_robustness_spec.rb +4 -2
  61. data/spec/connection_spec.rb +37 -0
  62. data/spec/coverage_helper.rb +39 -0
  63. data/spec/data/marshall.yaml +1667 -0
  64. data/spec/data_spec.rb +673 -0
  65. data/spec/dbus_spec.rb +22 -0
  66. data/spec/emits_changed_signal_spec.rb +58 -0
  67. data/spec/err_msg_spec.rb +2 -0
  68. data/spec/introspect_xml_parser_spec.rb +2 -0
  69. data/spec/introspection_spec.rb +2 -0
  70. data/spec/main_loop_spec.rb +17 -1
  71. data/spec/message_spec.rb +21 -0
  72. data/spec/mock-service/cockpit-dbustests.rb +29 -0
  73. data/spec/mock-service/com.redhat.Cockpit.DBusTests.xml +180 -0
  74. data/spec/mock-service/org.ruby.service.service +4 -0
  75. data/spec/mock-service/org.rubygems.ruby_dbus.DBusTests.service +4 -0
  76. data/spec/{service_newapi.rb → mock-service/spaghetti-monster.rb} +122 -75
  77. data/spec/node_spec.rb +65 -0
  78. data/spec/object_manager_spec.rb +33 -0
  79. data/spec/object_path_spec.rb +3 -0
  80. data/spec/object_server_spec.rb +138 -0
  81. data/spec/object_spec.rb +194 -0
  82. data/spec/packet_marshaller_spec.rb +41 -0
  83. data/spec/packet_unmarshaller_spec.rb +248 -0
  84. data/spec/platform_spec.rb +14 -0
  85. data/spec/property_spec.rb +214 -12
  86. data/spec/proxy_object_interface_spec.rb +35 -0
  87. data/spec/proxy_object_spec.rb +2 -0
  88. data/spec/{bus_driver_spec.rb → proxy_service_spec.rb} +15 -8
  89. data/spec/raw_message_spec.rb +32 -0
  90. data/spec/server_robustness_spec.rb +20 -6
  91. data/spec/session_bus_spec.rb +36 -1
  92. data/spec/session_bus_spec_manual.rb +2 -0
  93. data/spec/signal_spec.rb +16 -3
  94. data/spec/spec_helper.rb +25 -33
  95. data/spec/thread_safety_spec.rb +55 -12
  96. data/spec/tools/dbus-launch-simple +9 -6
  97. data/spec/tools/dbus-limited-session.conf +32 -0
  98. data/spec/tools/test_env +26 -6
  99. data/spec/type_spec.rb +214 -6
  100. data/spec/value_spec.rb +16 -1
  101. data/spec/variant_spec.rb +4 -2
  102. data/spec/zzz_quit_spec.rb +16 -0
  103. metadata +92 -20
  104. data/examples/gdbus/gdbus +0 -257
  105. data/examples/gdbus/gdbus.glade +0 -98
  106. data/examples/gdbus/launch.sh +0 -4
  107. data/spec/server_spec.rb +0 -53
  108. data/spec/tools/test_server +0 -39
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This file is part of the ruby-dbus project
2
4
  # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
3
5
  #
@@ -27,7 +29,7 @@ module DBus
27
29
  attr_accessor :path
28
30
  # The destination filter.
29
31
  attr_accessor :destination
30
- # The type type that is matched.
32
+ # @return [String] The type type that is matched.
31
33
  attr_reader :type
32
34
 
33
35
  # Create a new match rule
@@ -35,13 +37,14 @@ module DBus
35
37
  @sender = @interface = @member = @path = @destination = @type = nil
36
38
  end
37
39
 
38
- # Set the message types to filter to type _t_.
40
+ # Set the message types to filter to type _typ_.
39
41
  # Possible message types are: signal, method_call, method_return, and error.
40
- def type=(t)
41
- if !["signal", "method_call", "method_return", "error"].member?(t)
42
- raise MatchRuleException, t
42
+ def type=(typ)
43
+ if !["signal", "method_call", "method_return", "error"].member?(typ)
44
+ raise MatchRuleException, typ
43
45
  end
44
- @type = t
46
+
47
+ @type = typ
45
48
  end
46
49
 
47
50
  # Returns a match rule string version of the object. E.g.:
@@ -58,11 +61,13 @@ module DBus
58
61
  def from_s(str)
59
62
  str.split(",").each do |eq|
60
63
  next unless eq =~ /^(.*)='([^']*)'$/
64
+
61
65
  # "
62
66
  name = Regexp.last_match(1)
63
67
  val = Regexp.last_match(2)
64
68
  raise MatchRuleException, name unless FILTERS.member?(name.to_sym)
65
- method(name + "=").call(val)
69
+
70
+ method("#{name}=").call(val)
66
71
  end
67
72
  self
68
73
  end
@@ -80,18 +85,13 @@ module DBus
80
85
 
81
86
  # Determines whether a message _msg_ matches the match rule.
82
87
  def match(msg)
83
- if @type
84
- if { Message::SIGNAL => "signal", Message::METHOD_CALL => "method_call",
85
- Message::METHOD_RETURN => "method_return",
86
- Message::ERROR => "error" }[msg.message_type] != @type
87
- return false
88
- end
89
- end
88
+ return false if @type && @type != msg.message_type_s
90
89
  return false if @interface && @interface != msg.interface
91
90
  return false if @member && @member != msg.member
92
91
  return false if @path && @path != msg.path
92
+
93
93
  # FIXME: sender and destination are ignored
94
94
  true
95
95
  end
96
- end # class MatchRule
97
- end # module D-Bus
96
+ end
97
+ end
data/lib/dbus/message.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # dbus.rb - Module containing the low-level D-Bus implementation
2
4
  #
3
5
  # This file is part of the ruby-dbus project
@@ -8,16 +10,12 @@
8
10
  # License, version 2.1 as published by the Free Software Foundation.
9
11
  # See the file "COPYING" for the exact licensing terms.
10
12
 
13
+ require_relative "raw_message"
14
+
11
15
  # = D-Bus main module
12
16
  #
13
17
  # Module containing all the D-Bus modules and classes.
14
18
  module DBus
15
- # = InvalidDestinationName class
16
- # Thrown when you try to send a message to /org/freedesktop/DBus/Local, that
17
- # is reserved.
18
- class InvalidDestinationName < Exception
19
- end
20
-
21
19
  # = D-Bus message class
22
20
  #
23
21
  # Class that holds any type of message that travels over the bus.
@@ -27,7 +25,7 @@ module DBus
27
25
  # Mutex that protects updates on the serial number.
28
26
  @@serial_mutex = Mutex.new
29
27
  # Type of a message (by specification).
30
- MESSAGE_SIGNATURE = "yyyyuua(yv)".freeze
28
+ MESSAGE_SIGNATURE = "yyyyuua(yv)"
31
29
 
32
30
  # FIXME: following message type constants should be under Message::Type IMO
33
31
  # well, yeah sure
@@ -43,6 +41,9 @@ module DBus
43
41
  # Signal message type.
44
42
  SIGNAL = 4
45
43
 
44
+ # Names used by signal match rules
45
+ TYPE_NAMES = ["invalid", "method_call", "method_return", "error", "signal"].freeze
46
+
46
47
  # Message flag signyfing that no reply is expected.
47
48
  NO_REPLY_EXPECTED = 0x1
48
49
  # Message flag signifying that no automatic start is required/must be
@@ -66,11 +67,13 @@ module DBus
66
67
  attr_accessor :sender
67
68
  # The signature of the message contents.
68
69
  attr_accessor :signature
69
- # The serial number of the message this message is a reply for.
70
+ # @return [Integer] (u32)
71
+ # The serial number of the message this message is a reply for.
70
72
  attr_accessor :reply_serial
71
73
  # The protocol.
72
74
  attr_reader :protocol
73
- # The serial of the message.
75
+ # @return [Integer] (u32)
76
+ # The serial of the message.
74
77
  attr_reader :serial
75
78
  # The parameters of the message.
76
79
  attr_reader :params
@@ -100,27 +103,33 @@ module DBus
100
103
 
101
104
  def to_s
102
105
  "#{message_type} sender=#{sender} -> dest=#{destination} " \
103
- "serial=#{serial} reply_serial=#{reply_serial} " \
104
- "path=#{path}; interface=#{interface}; member=#{member} " \
105
- "error_name=#{error_name}"
106
+ "serial=#{serial} reply_serial=#{reply_serial} " \
107
+ "path=#{path}; interface=#{interface}; member=#{member} " \
108
+ "error_name=#{error_name}"
109
+ end
110
+
111
+ # @return [String] name of message type, as used in match rules:
112
+ # "method_call", "method_return", "signal", "error"
113
+ def message_type_s
114
+ TYPE_NAMES[message_type] || "unknown_type_#{message_type}"
106
115
  end
107
116
 
108
- # Create a regular reply to a message _m_.
109
- def self.method_return(m)
110
- MethodReturnMessage.new.reply_to(m)
117
+ # Create a regular reply to a message _msg_.
118
+ def self.method_return(msg)
119
+ MethodReturnMessage.new.reply_to(msg)
111
120
  end
112
121
 
113
- # Create an error reply to a message _m_.
114
- def self.error(m, error_name, description = nil)
115
- ErrorMessage.new(error_name, description).reply_to(m)
122
+ # Create an error reply to a message _msg_.
123
+ def self.error(msg, error_name, description = nil)
124
+ ErrorMessage.new(error_name, description).reply_to(msg)
116
125
  end
117
126
 
118
- # Mark this message as a reply to a another message _m_, taking
119
- # the serial number of _m_ as reply serial and the sender of _m_ as
127
+ # Mark this message as a reply to a another message _msg_, taking
128
+ # the serial number of _msg_ as reply serial and the sender of _msg_ as
120
129
  # destination.
121
- def reply_to(m)
122
- @reply_serial = m.serial
123
- @destination = m.sender
130
+ def reply_to(msg)
131
+ @reply_serial = msg.serial
132
+ @destination = msg.sender
124
133
  self
125
134
  end
126
135
 
@@ -131,6 +140,10 @@ module DBus
131
140
  @params << [type, val]
132
141
  end
133
142
 
143
+ # "l" or "B"
144
+ ENDIANNESS_CHAR = ENV.fetch("RUBY_DBUS_ENDIANNESS", HOST_END)
145
+ ENDIANNESS = RawMessage.endianness(ENDIANNESS_CHAR)
146
+
134
147
  # FIXME: what are these? a message element constant enumeration?
135
148
  # See method below, in a message, you have and array of optional parameters
136
149
  # that come with an index, to determine their meaning. The values are in
@@ -145,21 +158,25 @@ module DBus
145
158
  SENDER = 7
146
159
  SIGNATURE = 8
147
160
 
161
+ RESERVED_PATH = "/org/freedesktop/DBus/Local"
162
+
148
163
  # Marshall the message with its current set parameters and return
149
164
  # it in a packet form.
165
+ # @return [String]
150
166
  def marshall
151
- if @path == "/org/freedesktop/DBus/Local"
152
- raise InvalidDestinationName
167
+ if @path == RESERVED_PATH
168
+ # the bus would disconnect us, better explain why
169
+ raise "Cannot send a message with the reserved path #{RESERVED_PATH}: #{inspect}"
153
170
  end
154
171
 
155
- params = PacketMarshaller.new
156
- @params.each do |param|
157
- params.append(param[0], param[1])
172
+ params_marshaller = PacketMarshaller.new(endianness: ENDIANNESS)
173
+ @params.each do |type, value|
174
+ params_marshaller.append(type, value)
158
175
  end
159
- @body_length = params.packet.bytesize
176
+ @body_length = params_marshaller.packet.bytesize
160
177
 
161
- marshaller = PacketMarshaller.new
162
- marshaller.append(Type::BYTE, HOST_END)
178
+ marshaller = PacketMarshaller.new(endianness: ENDIANNESS)
179
+ marshaller.append(Type::BYTE, ENDIANNESS_CHAR.ord)
163
180
  marshaller.append(Type::BYTE, @message_type)
164
181
  marshaller.append(Type::BYTE, @flags)
165
182
  marshaller.append(Type::BYTE, @protocol)
@@ -178,10 +195,8 @@ module DBus
178
195
  marshaller.append("a(yv)", headers)
179
196
 
180
197
  marshaller.align(8)
181
- @params.each do |param|
182
- marshaller.append(param[0], param[1])
183
- end
184
- marshaller.packet
198
+
199
+ marshaller.packet + params_marshaller.packet
185
200
  end
186
201
 
187
202
  # Unmarshall a packet contained in the buffer _buf_ and set the
@@ -191,13 +206,7 @@ module DBus
191
206
  # the detected message (self) and
192
207
  # the index pointer of the buffer where the message data ended.
193
208
  def unmarshall_buffer(buf)
194
- buf = buf.dup
195
- endianness = if buf[0] == "l"
196
- LIL_END
197
- else
198
- BIG_END
199
- end
200
- pu = PacketUnmarshaller.new(buf, endianness)
209
+ pu = PacketUnmarshaller.new(buf, RawMessage.endianness(buf[0]))
201
210
  mdata = pu.unmarshall(MESSAGE_SIGNATURE)
202
211
  _, @message_type, @flags, @protocol, @body_length, @serial,
203
212
  headers = mdata
@@ -222,21 +231,21 @@ module DBus
222
231
  @signature = struct[1]
223
232
  end
224
233
  end
225
- pu.align(8)
226
- if @body_length > 0 && @signature
234
+ pu.align_body
235
+ if @body_length.positive? && @signature
227
236
  @params = pu.unmarshall(@signature, @body_length)
228
237
  end
229
- [self, pu.idx]
238
+ [self, pu.consumed_size]
230
239
  end
231
240
 
232
241
  # Make a new exception from ex, mark it as being caused by this message
233
242
  # @api private
234
- def annotate_exception(ex)
235
- new_ex = ex.exception("#{ex}; caused by #{self}")
236
- new_ex.set_backtrace(ex.backtrace)
237
- new_ex
243
+ def annotate_exception(exc)
244
+ new_exc = exc.exception("#{exc}; caused by #{self}")
245
+ new_exc.set_backtrace(exc.backtrace)
246
+ new_exc
238
247
  end
239
- end # class Message
248
+ end
240
249
 
241
250
  class MethodReturnMessage < Message
242
251
  def initialize
@@ -251,17 +260,17 @@ module DBus
251
260
  add_param(Type::STRING, description) unless description.nil?
252
261
  end
253
262
 
254
- def self.from_exception(ex)
255
- name = if ex.is_a? DBus::Error
256
- ex.name
263
+ def self.from_exception(exc)
264
+ name = if exc.is_a? DBus::Error
265
+ exc.name
257
266
  else
258
267
  "org.freedesktop.DBus.Error.Failed"
259
- # ex.class.to_s # RuntimeError is not a valid name, has no dot
268
+ # exc.class.to_s # RuntimeError is not a valid name, has no dot
260
269
  end
261
- description = ex.message
270
+ description = exc.message
262
271
  msg = new(name, description)
263
- msg.add_param(DBus.type("as"), ex.backtrace)
272
+ msg.add_param(DBus.type("as"), exc.backtrace)
264
273
  msg
265
274
  end
266
275
  end
267
- end # module DBus
276
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This file is part of the ruby-dbus project
2
4
  # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
3
5
  # Copyright (C) 2009-2014 Martin Vidner
@@ -16,35 +18,49 @@ module DBus
16
18
  # The socket that is used to connect with the bus.
17
19
  attr_reader :socket
18
20
 
21
+ # The buffer size for messages.
22
+ MSG_BUF_SIZE = 4096
23
+
19
24
  def initialize(address)
25
+ DBus.logger.debug "MessageQueue: #{address}"
20
26
  @address = address
21
27
  @buffer = ""
28
+ # Reduce allocations by using a single buffer for our socket
29
+ @read_buffer = String.new(capacity: MSG_BUF_SIZE)
22
30
  @is_tcp = false
31
+ @mutex = Mutex.new
23
32
  connect
24
33
  end
25
34
 
26
- # @param non_block [Boolean] if true, return nil instead of waiting
35
+ # @param blocking [Boolean]
36
+ # true: wait to return a {Message};
37
+ # false: may return `nil`
27
38
  # @return [Message,nil] one message or nil if unavailable
28
39
  # @raise EOFError
29
40
  # @todo failure modes
30
- def pop(non_block = false)
31
- buffer_from_socket_nonblock
32
- message = message_from_buffer_nonblock
33
- unless non_block
34
- # we can block
35
- while message.nil?
36
- r, _d, _d = IO.select([@socket])
37
- if r && r[0] == @socket
38
- buffer_from_socket_nonblock
39
- message = message_from_buffer_nonblock
41
+ def pop(blocking: true)
42
+ # FIXME: this is not enough, the R/W test deadlocks on shared connections
43
+ @mutex.synchronize do
44
+ buffer_from_socket_nonblock
45
+ message = message_from_buffer_nonblock
46
+ if blocking
47
+ # we can block
48
+ while message.nil?
49
+ r, _d, _d = IO.select([@socket])
50
+ if r && r[0] == @socket
51
+ buffer_from_socket_nonblock
52
+ message = message_from_buffer_nonblock
53
+ end
40
54
  end
41
55
  end
56
+ message
42
57
  end
43
- message
44
58
  end
45
59
 
46
60
  def push(message)
47
- @socket.write(message.marshall)
61
+ @mutex.synchronize do
62
+ @socket.write(message.marshall)
63
+ end
48
64
  end
49
65
  alias << push
50
66
 
@@ -54,7 +70,7 @@ module DBus
54
70
  def connect
55
71
  addresses = @address.split ";"
56
72
  # connect to first one that succeeds
57
- worked = addresses.find do |a|
73
+ addresses.find do |a|
58
74
  transport, keyvaluestring = a.split ":"
59
75
  kv_list = keyvaluestring.split ","
60
76
  kv_hash = {}
@@ -74,29 +90,29 @@ module DBus
74
90
  # ignore, report?
75
91
  end
76
92
  end
77
- worked
78
93
  # returns the address that worked or nil.
79
94
  # how to report failure?
80
95
  end
81
96
 
82
97
  # Connect to a bus over tcp and initialize the connection.
83
98
  def connect_to_tcp(params)
84
- # check if the path is sufficient
85
- if params.key?("host") && params.key?("port")
99
+ host = params["host"]
100
+ port = params["port"]
101
+ if host && port
86
102
  begin
87
103
  # initialize the tcp socket
88
- @socket = TCPSocket.new(params["host"], params["port"].to_i)
104
+ @socket = TCPSocket.new(host, port.to_i)
89
105
  @socket.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
90
106
  init_connection
91
107
  @is_tcp = true
92
108
  rescue Exception => e
93
109
  puts "Oops:", e
94
- puts "Error: Could not establish connection to: #{@path}, will now exit."
110
+ puts "Error: Could not establish connection to: #{host}:#{port}, will now exit."
95
111
  exit(1) # a little harsh
96
112
  end
97
113
  else
98
114
  # Danger, Will Robinson: the specified "path" is not usable
99
- puts "Error: supplied path: #{@path}, unusable! sorry."
115
+ puts "Error: supplied params: #{@params}, unusable! sorry."
100
116
  end
101
117
  end
102
118
 
@@ -125,7 +141,7 @@ module DBus
125
141
 
126
142
  # Initialize the connection to the bus.
127
143
  def init_connection
128
- client = Client.new(@socket)
144
+ client = Authentication::Client.new(@socket)
129
145
  client.authenticate
130
146
  end
131
147
 
@@ -135,6 +151,7 @@ module DBus
135
151
  # @return [Message,nil] the message or nil if unavailable
136
152
  def message_from_buffer_nonblock
137
153
  return nil if @buffer.empty?
154
+
138
155
  ret = nil
139
156
  begin
140
157
  ret, size = Message.new.unmarshall_buffer(@buffer)
@@ -145,15 +162,12 @@ module DBus
145
162
  ret
146
163
  end
147
164
 
148
- # The buffer size for messages.
149
- MSG_BUF_SIZE = 4096
150
-
151
165
  # Fill (append) the buffer from data that might be available on the
152
166
  # socket.
153
167
  # @return [void]
154
168
  # @raise EOFError
155
169
  def buffer_from_socket_nonblock
156
- @buffer += @socket.read_nonblock(MSG_BUF_SIZE)
170
+ @buffer += @socket.read_nonblock(MSG_BUF_SIZE, @read_buffer)
157
171
  rescue EOFError
158
172
  raise # the caller expects it
159
173
  rescue Errno::EAGAIN
@@ -161,6 +175,7 @@ module DBus
161
175
  rescue Exception => e
162
176
  puts "Oops:", e
163
177
  raise if @is_tcp # why?
178
+
164
179
  puts "WARNING: read_nonblock failed, falling back to .recv"
165
180
  @buffer += @socket.recv(MSG_BUF_SIZE)
166
181
  end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file is part of the ruby-dbus project
4
+ # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
5
+ # Copyright (C) 2023 Martin Vidner
6
+ #
7
+ # This library is free software; you can redistribute it and/or
8
+ # modify it under the terms of the GNU Lesser General Public
9
+ # License, version 2.1 as published by the Free Software Foundation.
10
+ # See the file "COPYING" for the exact licensing terms.
11
+
12
+ module DBus
13
+ # Has a tree of {Node}s, refering to {Object}s or to {ProxyObject}s.
14
+ class NodeTree
15
+ # @return [Node]
16
+ attr_reader :root
17
+
18
+ def initialize
19
+ @root = Node.new("/")
20
+ end
21
+
22
+ # Get the object node corresponding to the given *path*.
23
+ # @param path [ObjectPath]
24
+ # @param create [Boolean] if true, the the {Node}s in the path are created
25
+ # if they do not already exist.
26
+ # @return [Node,nil]
27
+ def get_node(path, create: false)
28
+ n = @root
29
+ path.sub(%r{^/}, "").split("/").each do |elem|
30
+ if !(n[elem])
31
+ return nil if !create
32
+
33
+ n[elem] = Node.new(elem)
34
+ end
35
+ n = n[elem]
36
+ end
37
+ n
38
+ end
39
+ end
40
+
41
+ # = Object path node class
42
+ #
43
+ # Class representing a node on an object path.
44
+ class Node < Hash
45
+ # @return [DBus::Object,DBus::ProxyObject,nil]
46
+ # The D-Bus object contained by the node.
47
+ attr_accessor :object
48
+
49
+ # The name of the node.
50
+ # @return [String] the last component of its object path, or "/"
51
+ attr_reader :name
52
+
53
+ # Create a new node with a given _name_.
54
+ def initialize(name)
55
+ super()
56
+ @name = name
57
+ @object = nil
58
+ end
59
+
60
+ # Return an XML string representation of the node.
61
+ # It is shallow, not recursing into subnodes
62
+ # @param node_opath [String]
63
+ def to_xml(node_opath)
64
+ xml = '<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
65
+ "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
66
+ '
67
+ xml += "<node name=\"#{node_opath}\">\n"
68
+ each_key do |k|
69
+ xml += " <node name=\"#{k}\" />\n"
70
+ end
71
+ @object&.intfs&.each_value do |v|
72
+ xml += v.to_xml
73
+ end
74
+ xml += "</node>"
75
+ xml
76
+ end
77
+
78
+ # Return inspect information of the node.
79
+ def inspect
80
+ # Need something here
81
+ "<DBus::Node #{sub_inspect}>"
82
+ end
83
+
84
+ # Return instance inspect information, used by Node#inspect.
85
+ def sub_inspect
86
+ s = ""
87
+ if !@object.nil?
88
+ s += format("%x ", @object.object_id)
89
+ end
90
+ contents_sub_inspect = keys
91
+ .map { |k| "#{k} => #{self[k].sub_inspect}" }
92
+ .join(",")
93
+ "#{s}{#{contents_sub_inspect}}"
94
+ end
95
+
96
+ # All objects (not paths) under this path (except itself).
97
+ # @return [Array<DBus::Object>]
98
+ def descendant_objects
99
+ children_objects = values.map(&:object).compact
100
+ descendants = values.map(&:descendant_objects)
101
+ flat_descendants = descendants.reduce([], &:+)
102
+ children_objects + flat_descendants
103
+ end
104
+ end
105
+ end