druzy-upnp 1.0.0 → 2.0.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.
@@ -1,16 +0,0 @@
1
- require 'socket'
2
-
3
- # Workaround for missing constants on Windows
4
- module Socket::Constants
5
- IP_ADD_MEMBERSHIP = 12 unless defined? IP_ADD_MEMBERSHIP
6
- IP_MULTICAST_LOOP = 11 unless defined? IP_MULTICAST_LOOP
7
- IP_MULTICAST_TTL = 10 unless defined? IP_MULTICAST_TTL
8
- IP_TTL = 4 unless defined? IP_TTL
9
- end
10
-
11
- class Socket
12
- IP_ADD_MEMBERSHIP = 12 unless defined? IP_ADD_MEMBERSHIP
13
- IP_MULTICAST_LOOP = 11 unless defined? IP_MULTICAST_LOOP
14
- IP_MULTICAST_TTL = 10 unless defined? IP_MULTICAST_TTL
15
- IP_TTL = 4 unless defined? IP_TTL
16
- end
@@ -1,65 +0,0 @@
1
- class Hash
2
-
3
- # Converts Hash search targets to SSDP search target String. Conversions are
4
- # as follows:
5
- # uuid: "someUUID" # => "uuid:someUUID"
6
- # device_type: "someDeviceType:1" # => "urn:schemas-upnp-org:device:someDeviceType:1"
7
- # service_type: "someServiceType:2" # => "urn:schemas-upnp-org:service:someServiceType:2"
8
- #
9
- # You can use custom UPnP domain names too:
10
- # { device_type: "someDeviceType:3",
11
- # domain_name: "mydomain-com" } # => "urn:my-domain:device:someDeviceType:3"
12
- # { service_type: "someServiceType:4",
13
- # domain_name: "mydomain-com" } # => "urn:my-domain:service:someDeviceType:4"
14
- #
15
- # @return [String] The converted String, according to the UPnP spec.
16
- def to_upnp_s
17
- if self.has_key? :uuid
18
- return "uuid:#{self[:uuid]}"
19
- elsif self.has_key? :device_type
20
- if self.has_key? :domain_name
21
- return "urn:#{self[:domain_name]}:device:#{self[:device_type]}"
22
- else
23
- return "urn:schemas-upnp-org:device:#{self[:device_type]}"
24
- end
25
- elsif self.has_key? :service_type
26
- if self.has_key? :domain_name
27
- return "urn:#{self[:domain_name]}:service:#{self[:service_type]}"
28
- else
29
- return "urn:schemas-upnp-org:service:#{self[:service_type]}"
30
- end
31
- else
32
- self.to_s
33
- end
34
- end
35
- end
36
-
37
-
38
- class Symbol
39
-
40
- # Converts Symbol search targets to SSDP search target String. Conversions are
41
- # as follows:
42
- # :all # => "ssdp:all"
43
- # :root # => "upnp:rootdevice"
44
- # "root" # => "upnp:rootdevice"
45
- #
46
- # @return [String] The converted String, according to the UPnP spec.
47
- def to_upnp_s
48
- if self == :all
49
- 'ssdp:all'
50
- elsif self == :root
51
- 'upnp:rootdevice'
52
- else
53
- self
54
- end
55
- end
56
- end
57
-
58
-
59
- class String
60
- # This doesn't do anything to the string; just allows users to call the
61
- # method without having to check type first.
62
- def to_upnp_s
63
- self
64
- end
65
- end
@@ -1,70 +0,0 @@
1
- require 'nori'
2
- require 'em-http-request'
3
- require_relative 'error'
4
- require_relative '../../upnp'
5
-
6
- module Druzy
7
- module Upnp
8
- class ControlPoint
9
- class Base
10
-
11
- protected
12
-
13
- def get_description(location, description_getter)
14
- http = EM::HttpRequest.new(location).aget
15
-
16
- t = EM::Timer.new(30) do
17
- http.fail(:timeout)
18
- end
19
-
20
- http.errback do |error|
21
- if error == :timeout
22
- http = EM::HttpRequest.new(location).get
23
- else
24
- description_getter.set_deferred_status(:failed)
25
-
26
- if ControlPoint.raise_on_remote_error
27
- raise ControlPoint::Error, "Unable to retrieve DDF from #{location}"
28
- end
29
- end
30
- end
31
-
32
- http.callback {
33
- if http.response_header.status != 200
34
- description_getter.set_deferred_status(:failed)
35
- else
36
- response = xml_parser.parse(http.response)
37
- description_getter.set_deferred_status(:succeeded, response)
38
- end
39
- }
40
- end
41
-
42
- def build_url(url_base, rest_of_url)
43
- if url_base.end_with?('/') && rest_of_url.start_with?('/')
44
- rest_of_url.sub!('/', '')
45
- end
46
-
47
- url_base + rest_of_url
48
- end
49
-
50
- # @return [Nori::Parser]
51
- def xml_parser
52
- @xml_parser if @xml_parser
53
-
54
- options = {
55
- convert_tags_to: lambda { |tag| tag.to_sym }
56
- }
57
-
58
- begin
59
- require 'nokogiri'
60
- options.merge! parser: :nokogiri
61
- rescue LoadError
62
- warn "Tried loading nokogiri for XML couldn't. This is OK, just letting you know."
63
- end
64
-
65
- @xml_parser = Nori.new(options)
66
- end
67
- end
68
- end
69
- end
70
- end
@@ -1,465 +0,0 @@
1
- require_relative 'base'
2
- require_relative 'service'
3
- require_relative 'error'
4
- require 'uri'
5
- require 'eventmachine'
6
- require 'time'
7
-
8
- module Druzy
9
- module Upnp
10
- class ControlPoint
11
- class Device < Base
12
- include EM::Deferrable
13
-
14
- attr_reader :ssdp_notification
15
-
16
- #vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
17
- # Passed in as part of +device_info+; given by the SSDP search response.
18
- #
19
- attr_reader :cache_control
20
- attr_reader :date
21
- attr_reader :ext
22
- attr_reader :location
23
- attr_reader :server
24
- attr_reader :st
25
- attr_reader :usn
26
- #
27
- # DONE +device_info+
28
- #^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
29
-
30
- # @return [Hash] The whole parsed description... just in case.
31
- attr_reader :description
32
-
33
- attr_reader :expiration
34
-
35
- #vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
36
- # Determined by device description file.
37
- #
38
-
39
- #------- <root> elements -------
40
-
41
- # @return [String] The xmlns attribute of the device from the description file.
42
- attr_reader :xmlns
43
-
44
- # @return [Hash] :major and :minor revisions of the UPnP spec this device adheres to.
45
- attr_reader :spec_version
46
-
47
- # @return [String] URLBase from the device's description file.
48
- attr_reader :url_base
49
-
50
- #------- <root><device> elements -------
51
-
52
- # @return [String] The type of UPnP device (URN) from the description file.
53
- attr_reader :device_type
54
-
55
- # @return [String] Short device description for the end user.
56
- attr_reader :friendly_name
57
-
58
- # @return [String] Manufacturer's name.
59
- attr_reader :manufacturer
60
-
61
- # @return [String] Manufacturer's web site.
62
- attr_reader :manufacturer_url
63
-
64
- # @return [String] Long model description for the end user, from the description file.
65
- attr_reader :model_description
66
-
67
- # @return [String] Model name of this device from the description file.
68
- attr_reader :model_name
69
-
70
- # @return [String] Model number of this device from the description file.
71
- attr_reader :model_number
72
-
73
- # @return [String] Web site for model of this device.
74
- attr_reader :model_url
75
-
76
- # @return [String] The serial number from the description file.
77
- attr_reader :serial_number
78
-
79
- # @return [String] The UDN for the device, from the description file.
80
- attr_reader :udn
81
-
82
- # @return [String] The UPC of the device from the description file.
83
- attr_reader :upc
84
-
85
- # @return [Array<Hash>] An Array where each element is a Hash that describes an icon.
86
- attr_reader :icon_list
87
-
88
- # Services provided directly by this device.
89
- attr_reader :service_list
90
-
91
- # Devices embedded directly into this device.
92
- attr_reader :device_list
93
-
94
- # @return [String] URL for device control via a browser.
95
- attr_reader :presentation_url
96
-
97
- #
98
- # DONE description file
99
- #^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
100
-
101
- # @param [Hash] device_info
102
- # @option device_info [Hash] ssdp_notification
103
- # @option device_info [Hash] device_description
104
- # @option device_info [Hash] parent_base_url
105
- def initialize(device_info)
106
- super()
107
-
108
- @device_info = device_info
109
- @device_list = []
110
- @service_list = []
111
- @icon_list = []
112
- @xmlns = ''
113
- @done_creating_devices = false
114
- @done_creating_services = false
115
- end
116
-
117
- def fetch
118
- description_getter = EventMachine::DefaultDeferrable.new
119
-
120
- description_getter.errback do
121
- msg = 'Failed getting description.'
122
- @done_creating_devices = true
123
- @done_creating_services = true
124
- set_deferred_status(:failed, msg)
125
-
126
- if ControlPoint.raise_on_remote_error
127
- raise ControlPoint::Error, msg
128
- end
129
- end
130
-
131
- if @device_info.has_key? :ssdp_notification
132
- extract_from_ssdp_notification(description_getter)
133
- elsif @device_info.has_key? :device_description
134
- description_getter.set_deferred_success @device_info[:device_description]
135
- else
136
- description_getter.set_deferred_failure
137
- end
138
-
139
- description_getter.callback do |description|
140
- @description = description
141
-
142
- if @description.nil?
143
- set_deferred_status(:failed, 'Got back an empty description...')
144
- return
145
- end
146
-
147
- extract_spec_version
148
-
149
- @url_base = extract_url_base
150
-
151
- if @device_info[:ssdp_notification]
152
- @xmlns = @description[:root][:@xmlns]
153
- extract_description(@description[:root][:device])
154
- elsif @device_info.has_key? :device_description
155
- extract_description(@description)
156
- end
157
- end
158
-
159
- tickloop = EM.tick_loop do
160
- if @done_creating_devices && @done_creating_services
161
- :stop
162
- end
163
- end
164
-
165
- tickloop.on_stop { set_deferred_status :succeeded, self }
166
- end
167
-
168
- def extract_from_ssdp_notification(callback)
169
- @ssdp_notification = @device_info[:ssdp_notification]
170
-
171
- @cache_control = @ssdp_notification[:cache_control]
172
- @location = ssdp_notification[:location]
173
- @server = ssdp_notification[:server]
174
- @st = ssdp_notification[:st] || ssdp_notification[:nt]
175
- @ext = ssdp_notification.has_key?(:ext) ? true : false
176
- @usn = ssdp_notification[:usn]
177
- @date = ssdp_notification[:date] || ''
178
- @expiration = if @date.empty?
179
- Time.now + @cache_control.match(/\d+/)[0].to_i
180
- else
181
- Time.at(Time.parse(@date).to_i + @cache_control.match(/\d+/)[0].to_i)
182
- end
183
-
184
- if @location
185
- get_description(@location, callback)
186
- else
187
- message = 'M-SEARCH response is either missing the Location header or has an empty value.'
188
- message << "Response: #{@ssdp_notification}"
189
-
190
- if ControlPoint.raise_on_remote_error
191
- raise ControlPoint::Error, message
192
- end
193
- end
194
- end
195
-
196
- def extract_url_base
197
- if @description[:root] && @description[:root][:URLBase]
198
- @description[:root][:URLBase]
199
- elsif @device_info[:parent_base_url]
200
- @device_info[:parent_base_url]
201
- else
202
- tmp_uri = URI(@location)
203
- "#{tmp_uri.scheme}://#{tmp_uri.host}:#{tmp_uri.port}/"
204
- end
205
- end
206
-
207
- # True if the device hasn't received an alive notification since it last
208
- # told its max age.
209
- def expired?
210
- Time.now > @expiration if @expiration
211
- end
212
-
213
- def has_devices?
214
- !@device_list.empty?
215
- end
216
-
217
- def has_services?
218
- !@service_list.empty?
219
- end
220
-
221
- def extract_description(ddf)
222
-
223
- @device_type = ddf[:deviceType] || ''
224
- @friendly_name = ddf[:friendlyName] || ''
225
- @manufacturer = ddf[:manufacturer] || ''
226
- @manufacturer_url = ddf[:manufacturerURL] || ''
227
- @model_description = ddf[:modelDescription] || ''
228
- @model_name = ddf[:modelName] || ''
229
- @model_number = ddf[:modelNumber] || ''
230
- @model_url = ddf[:modelURL] || ''
231
- @serial_number = ddf[:serialNumber] || ''
232
- @udn = ddf[:UDN] || ''
233
- @upc = ddf[:UPC] || ''
234
- @icon_list = extract_icons(ddf[:iconList])
235
- @presentation_url = ddf[:presentationURL] || ''
236
-
237
- start_device_extraction
238
- start_service_extraction
239
- end
240
-
241
- def extract_spec_version
242
- if @description[:root]
243
- "#{@description[:root][:specVersion][:major]}.#{@description[:root][:specVersion][:minor]}"
244
- end
245
- end
246
-
247
- def start_service_extraction
248
- services_extractor = EventMachine::DefaultDeferrable.new
249
-
250
- if @description[:serviceList]
251
- extract_services(@description[:serviceList], services_extractor)
252
- elsif @description[:root][:device][:serviceList]
253
- extract_services(@description[:root][:device][:serviceList], services_extractor)
254
- end
255
-
256
- services_extractor.errback do
257
- msg = 'Failed extracting services.'
258
- @done_creating_services = true
259
-
260
- if ControlPoint.raise_on_remote_error
261
- raise ControlPoint::Error, msg
262
- end
263
- end
264
-
265
- services_extractor.callback do |services|
266
- @service_list = services
267
-
268
- @done_creating_services = true
269
- end
270
- end
271
-
272
- def start_device_extraction
273
- device_extractor = EventMachine::DefaultDeferrable.new
274
- extract_devices(device_extractor)
275
-
276
- device_extractor.errback do
277
- msg = 'Failed extracting device.'
278
- @done_creating_devices = true
279
-
280
- if ControlPoint.raise_on_remote_error
281
- raise ControlPoint::Error, msg
282
- end
283
- end
284
-
285
- device_extractor.callback do |device|
286
- if device
287
- @device_list << device
288
- end
289
-
290
- @done_creating_devices = true
291
- end
292
- end
293
-
294
- # @return [Array<Hash>]
295
- def extract_icons(ddf_icon_list)
296
- return [] unless ddf_icon_list
297
-
298
- if ddf_icon_list[:icon].is_a? Array
299
- ddf_icon_list[:icon].map do |values|
300
- values[:url] = build_url(@url_base, values[:url])
301
- values
302
- end
303
- else
304
- [{
305
- mimetype: ddf_icon_list[:icon][:mimetype],
306
- width: ddf_icon_list[:icon][:width],
307
- height: ddf_icon_list[:icon][:height],
308
- depth: ddf_icon_list[:icon][:depth],
309
- url: build_url(@url_base, ddf_icon_list[:icon][:url])
310
- }]
311
- end
312
- end
313
-
314
- def extract_devices(group_device_extractor)
315
-
316
- device_list_hash = if @description.has_key? :root
317
-
318
- if @description[:root][:device][:deviceList]
319
- @description[:root][:device][:deviceList][:device]
320
- else
321
- group_device_extractor.set_deferred_status(:succeeded)
322
- end
323
- elsif @description[:deviceList]
324
- @description[:deviceList][:device]
325
- else
326
- group_device_extractor.set_deferred_status(:succeeded)
327
- end
328
-
329
- if device_list_hash.nil? || device_list_hash.empty?
330
- group_device_extractor.set_deferred_status(:succeeded)
331
- return
332
- end
333
-
334
- if device_list_hash.is_a? Array
335
- EM::Iterator.new(device_list_hash, device_list_hash.count).map(
336
- proc do |device, iter|
337
- single_device_extractor = EventMachine::DefaultDeferrable.new
338
-
339
- single_device_extractor.errback do
340
- msg = 'Failed extracting device.'
341
-
342
- if ControlPoint.raise_on_remote_error
343
- raise ControlPoint::Error, msg
344
- end
345
- end
346
-
347
- single_device_extractor.callback do |d|
348
- iter.return(d)
349
- end
350
-
351
- extract_device(device, single_device_extractor)
352
- end,
353
- proc do |found_devices|
354
- group_device_extractor.set_deferred_status(:succeeded, found_devices)
355
- end
356
- )
357
- else
358
- single_device_extractor = EventMachine::DefaultDeferrable.new
359
-
360
- single_device_extractor.errback do
361
- msg = 'Failed extracting device.'
362
- group_device_extractor.set_deferred_status(:failed, msg)
363
-
364
- if ControlPoint.raise_on_remote_error
365
- raise ControlPoint::Error, msg
366
- end
367
- end
368
-
369
- single_device_extractor.callback do |device|
370
- group_device_extractor.set_deferred_status(:succeeded, [device])
371
- end
372
-
373
- extract_device(device_list_hash, group_device_extractor)
374
- end
375
- end
376
-
377
- def extract_device(device, device_extractor)
378
- deferred_device = Device.new(device_description: device, parent_base_url: @url_base)
379
-
380
- deferred_device.errback do
381
- msg = "Couldn't build device!"
382
- device_extractor.set_deferred_status(:failed, msg)
383
-
384
- if ControlPoint.raise_on_remote_error
385
- raise ControlPoint::Error, msg
386
- end
387
- end
388
-
389
- deferred_device.callback do |built_device|
390
- device_extractor.set_deferred_status(:succeeded, built_device)
391
- end
392
-
393
- deferred_device.fetch
394
- end
395
-
396
- def extract_services(service_list, group_service_extractor)
397
- return if service_list.nil?
398
-
399
- service_list.each_value do |service|
400
- if service.is_a? Array
401
- EM::Iterator.new(service, service.count).map(
402
- proc do |s, iter|
403
- single_service_extractor = EventMachine::DefaultDeferrable.new
404
-
405
- single_service_extractor.errback do
406
- msg = 'Failed to create service.'
407
-
408
- if ControlPoint.raise_on_remote_error
409
- raise ControlPoint::Error, msg
410
- end
411
- end
412
-
413
- single_service_extractor.callback do |serv|
414
- iter.return(serv)
415
- end
416
-
417
- extract_service(s, single_service_extractor)
418
- end,
419
- proc do |found_services|
420
- group_service_extractor.set_deferred_status(:succeeded, found_services)
421
- end
422
- )
423
- else
424
- single_service_extractor = EventMachine::DefaultDeferrable.new
425
-
426
- single_service_extractor.errback do
427
- msg = 'Failed to create service.'
428
- group_service_extractor.set_deferred_status :failed, msg
429
-
430
- if ControlPoint.raise_on_remote_error
431
- raise ControlPoint::Error, msg
432
- end
433
- end
434
-
435
- single_service_extractor.callback do |s|
436
- group_service_extractor.set_deferred_status :succeeded, [s]
437
- end
438
-
439
- extract_service(service, single_service_extractor)
440
- end
441
- end
442
- end
443
-
444
- def extract_service(service, single_service_extractor)
445
- service_getter = Service.new(@url_base, service)
446
-
447
- service_getter.errback do |message|
448
- msg = "Couldn't build service with info: #{service}"
449
- single_service_extractor.set_deferred_status(:failed, msg)
450
-
451
- if ControlPoint.raise_on_remote_error
452
- raise ControlPoint::Error, message
453
- end
454
- end
455
-
456
- service_getter.callback do |built_service|
457
- single_service_extractor.set_deferred_status(:succeeded, built_service)
458
- end
459
-
460
- service_getter.fetch
461
- end
462
- end
463
- end
464
- end
465
- end
@@ -1,15 +0,0 @@
1
- module Druzy
2
- module Upnp
3
- class ControlPoint
4
- class Error < StandardError
5
- #
6
- end
7
-
8
- # Indicates an error occurred when performing a UPnP action while controlling
9
- # a device. See section 3.2 of the UPnP spec.
10
- class ActionError < StandardError
11
- #
12
- end
13
- end
14
- end
15
- end