rupnp 0.1.0 → 0.2.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.
- data/lib/rupnp.rb +2 -2
- data/lib/rupnp/control_point.rb +19 -8
- data/lib/rupnp/cp/remote_device.rb +48 -9
- data/spec/control_point_spec.rb +143 -0
- data/spec/spec_helper.rb +74 -16
- data/spec/ssdp/notifier_spec.rb +1 -1
- data/spec/ssdp/searcher_spec.rb +31 -0
- data/tasks/gem.rake +3 -1
- metadata +35 -2
data/lib/rupnp.rb
CHANGED
@@ -6,7 +6,7 @@ require 'eventmachine-le'
|
|
6
6
|
module RUPNP
|
7
7
|
|
8
8
|
# RUPNP version
|
9
|
-
VERSION = '0.
|
9
|
+
VERSION = '0.2.0'
|
10
10
|
|
11
11
|
@logdev = STDERR
|
12
12
|
@log_level = :info
|
@@ -59,4 +59,4 @@ require_relative 'rupnp/cp/event_server'
|
|
59
59
|
require_relative 'rupnp/cp/event_subscriber'
|
60
60
|
require_relative 'rupnp/ssdp'
|
61
61
|
|
62
|
-
|
62
|
+
require_relative 'rupnp/device'
|
data/lib/rupnp/control_point.rb
CHANGED
@@ -60,9 +60,13 @@ module RUPNP
|
|
60
60
|
# @return [void]
|
61
61
|
def start
|
62
62
|
search_devices_and_listen @search_target, @search_options
|
63
|
-
yield @new_device_channel, @bye_device_channel
|
63
|
+
yield @new_device_channel, @bye_device_channel if block_given?
|
64
64
|
end
|
65
65
|
|
66
|
+
# Start a search for devices. No listen for update is made.
|
67
|
+
#
|
68
|
+
# Found devices are accessible through {#devices}.
|
69
|
+
# @return [void]
|
66
70
|
def search_only
|
67
71
|
options = @search_options.dup
|
68
72
|
options[:search_only] = true
|
@@ -73,8 +77,8 @@ module RUPNP
|
|
73
77
|
# @param [Integer] port port to listen for
|
74
78
|
# @return [void]
|
75
79
|
def start_event_server(port=EVENT_SUB_DEFAULT_PORT)
|
76
|
-
@event_port
|
77
|
-
@add_event_url
|
80
|
+
@event_port ||= port
|
81
|
+
@add_event_url ||= EM::Channel.new
|
78
82
|
@event_server ||= EM.start_server('0.0.0.0', port, CP::EventServer,
|
79
83
|
@add_event_url)
|
80
84
|
end
|
@@ -83,7 +87,9 @@ module RUPNP
|
|
83
87
|
# @see #start_event_server
|
84
88
|
# @return [void]
|
85
89
|
def stop_event_server
|
90
|
+
@event_port = nil
|
86
91
|
EM.stop_server @event_server
|
92
|
+
@event_server = nil
|
87
93
|
end
|
88
94
|
|
89
95
|
# Add a device to the control point
|
@@ -129,14 +135,19 @@ module RUPNP
|
|
129
135
|
listener = SSDP.listen
|
130
136
|
|
131
137
|
listener.notifications.subscribe do |notification|
|
138
|
+
udn = usn2udn(notification['usn'])
|
139
|
+
|
132
140
|
case notification['nts']
|
133
|
-
when 'ssdp:alive'
|
134
|
-
|
141
|
+
when 'ssdp:alive', 'ssdp:update'
|
142
|
+
d = @devices.find { |d| d.udn == udn }
|
143
|
+
if d
|
144
|
+
d.update notification
|
145
|
+
else
|
146
|
+
create_device notification
|
147
|
+
end
|
135
148
|
when 'ssdp:byebye'
|
136
|
-
udn = usn2udn(notification['usn'])
|
137
149
|
log :info, "byebye notification sent by device #{udn}"
|
138
|
-
|
139
|
-
log :info, "#{rejected.udn} device removed" if rejected
|
150
|
+
@devices.reject! { |d| d.udn == udn }
|
140
151
|
else
|
141
152
|
log :warn, "Unknown notification type: #{notification['nts']}"
|
142
153
|
end
|
@@ -7,6 +7,12 @@ module RUPNP
|
|
7
7
|
# A device is a UPnP service provider.
|
8
8
|
# @author Sylvain Daubert.
|
9
9
|
class CP::RemoteDevice < CP::Base
|
10
|
+
|
11
|
+
# Number of seconds the advertisement is valid.
|
12
|
+
# Used when +ssdp:update+ advertisement is received but no
|
13
|
+
# previuous +ssdp:alive+ was received.
|
14
|
+
DEFAULT_MAX_AGE = 600
|
15
|
+
|
10
16
|
# Get control point which controls this device
|
11
17
|
# @return [ControlPoint]
|
12
18
|
attr_reader :control_point
|
@@ -34,6 +40,12 @@ module RUPNP
|
|
34
40
|
# Expiration time for the advertisement
|
35
41
|
# @return [Time]
|
36
42
|
attr_reader :expiration
|
43
|
+
# BOOTID.UPNP.ORG field value
|
44
|
+
# @return [Integer]
|
45
|
+
attr_reader :boot_id
|
46
|
+
# CONFIGID.UPNP.ORG field value
|
47
|
+
# @return [nil, Integer]
|
48
|
+
attr_reader :config_id
|
37
49
|
|
38
50
|
# UPnP version used by the device
|
39
51
|
# @return [String]
|
@@ -106,6 +118,16 @@ module RUPNP
|
|
106
118
|
# Get device from its description
|
107
119
|
# @return [void]
|
108
120
|
def fetch
|
121
|
+
if @notification['nextbootid.upnp.org']
|
122
|
+
@boot_id = @notification['nextbootid.upnp.org'].to_i
|
123
|
+
elsif @notification['bootid.upnp.org']
|
124
|
+
@boot_id = @notification['bootid.upnp.org'].to_i
|
125
|
+
else
|
126
|
+
fail self, 'no BOOTID.UPNP.ORG field. Message discarded.'
|
127
|
+
end
|
128
|
+
@config_id = @notification['confgid.upnp.org']
|
129
|
+
@config_id = @config_id.to_i if @config_id
|
130
|
+
|
109
131
|
description_getter = EM::DefaultDeferrable.new
|
110
132
|
|
111
133
|
description_getter.errback do
|
@@ -143,6 +165,17 @@ module RUPNP
|
|
143
165
|
end
|
144
166
|
end
|
145
167
|
|
168
|
+
# Update a device from a ssdp:update notification
|
169
|
+
# @param [String] notification
|
170
|
+
# @return [void]
|
171
|
+
def update(notification)
|
172
|
+
update_expiration notification
|
173
|
+
@boot_id = notification['nextbootid.upnp.org'].to_i
|
174
|
+
if notification['configid.upnp.org']
|
175
|
+
@config_id = notification['configid.upnp.org'].to_i
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
146
179
|
|
147
180
|
private
|
148
181
|
|
@@ -153,15 +186,8 @@ module RUPNP
|
|
153
186
|
@server = @notification['server']
|
154
187
|
@location = @notification['location']
|
155
188
|
@ext = @notification['ext']
|
156
|
-
@date = @notification['date'] || ''
|
157
|
-
@cache_control = @notification['cache-control'] || ''
|
158
189
|
|
159
|
-
|
160
|
-
@expiration = if @date.empty?
|
161
|
-
Time.now + max_age
|
162
|
-
else
|
163
|
-
Time.parse(@date) + max_age
|
164
|
-
end
|
190
|
+
update_expiration @notification
|
165
191
|
|
166
192
|
if @location
|
167
193
|
get_description @location, getter
|
@@ -204,12 +230,13 @@ module RUPNP
|
|
204
230
|
@model_number = device[:model_number] || ''
|
205
231
|
@model_url = device[:model_url] || ''
|
206
232
|
@serial_umber = device[:serial_number] || ''
|
207
|
-
@udn = device[:udn]
|
233
|
+
@udn = device[:udn].gsub(/uuid:/, '')
|
208
234
|
@upc = device[:upc] || ''
|
209
235
|
@presentation_url = device[:presentation_url] || ''
|
210
236
|
end
|
211
237
|
|
212
238
|
def extract_icons
|
239
|
+
return unless @description[:root][:device][:icon_list]
|
213
240
|
@description[:root][:device][:icon_list][:icon].each do |h|
|
214
241
|
icon = OpenStruct.new(h)
|
215
242
|
icon.url = build_url(@url_base, icon.url)
|
@@ -266,6 +293,18 @@ module RUPNP
|
|
266
293
|
end
|
267
294
|
end
|
268
295
|
|
296
|
+
def update_expiration(notification)
|
297
|
+
@date = @notification['date'] || ''
|
298
|
+
@cache_control = @notification['cache-control'] || ''
|
299
|
+
|
300
|
+
if @notification['nts'] == 'ssdp:alive'
|
301
|
+
max_age = @cache_control.match(/max-age\s*=\s*(\d+)/)[1].to_i
|
302
|
+
else
|
303
|
+
max_age = DEFAULT_MAX_AGE
|
304
|
+
end
|
305
|
+
@expiration = (@date.empty? ? Time.now : Time.parse(@date)) + max_age
|
306
|
+
end
|
307
|
+
|
269
308
|
end
|
270
309
|
|
271
310
|
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
|
3
|
+
module RUPNP
|
4
|
+
|
5
|
+
describe ControlPoint do
|
6
|
+
include EM::SpecHelper
|
7
|
+
|
8
|
+
let(:cp) { ControlPoint.new(:all, :response_wait_time => 1) }
|
9
|
+
let(:notify_options) { {
|
10
|
+
max_age: 10,
|
11
|
+
ip: '127.0.0.1',
|
12
|
+
port: 1234,
|
13
|
+
uuid: UUID.generate,
|
14
|
+
boot_id: 1,
|
15
|
+
config_id: 1,
|
16
|
+
u_search_port: DISCOVERY_PORT,
|
17
|
+
try_number: 1
|
18
|
+
} }
|
19
|
+
|
20
|
+
it 'should initialize a new instance' do
|
21
|
+
expect(cp.devices).to be_a(Array)
|
22
|
+
expect(cp.devices).to be_empty
|
23
|
+
end
|
24
|
+
|
25
|
+
[:search_only, :start].each do |meth|
|
26
|
+
it "##{meth} should detect devices" do
|
27
|
+
em do
|
28
|
+
uuid1 = UUID.generate
|
29
|
+
generate_search_responder uuid1, 1234
|
30
|
+
generate_search_responder uuid1, 1234
|
31
|
+
uuid2 = UUID.generate
|
32
|
+
generate_search_responder uuid2, 1235
|
33
|
+
|
34
|
+
stub_request(:get, '127.0.0.1:1234').to_return :headers => {
|
35
|
+
'SERVER' => 'OS/1.0 UPnP/1.1 TEST/1.0'
|
36
|
+
}, :body => generate_xml_device_description(uuid1)
|
37
|
+
stub_request(:get, '127.0.0.1:1235').to_return :headers => {
|
38
|
+
'SERVER' => 'OS/1.0 UPnP/1.1 TEST/1.0'
|
39
|
+
}, :body => generate_xml_device_description(uuid2)
|
40
|
+
|
41
|
+
cp.send meth
|
42
|
+
|
43
|
+
EM.add_timer(1) do
|
44
|
+
expect(cp.devices).to have(2).item
|
45
|
+
done
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
it '#search_only should not register devices after wait time is expired' do
|
52
|
+
em do
|
53
|
+
uuid = UUID.generate
|
54
|
+
stub_request(:get, '127.0.0.1:1234').to_return :headers => {
|
55
|
+
'SERVER' => 'OS/1.0 UPnP/1.1 TEST/1.0'
|
56
|
+
}, :body => generate_xml_device_description(uuid)
|
57
|
+
|
58
|
+
cp.search_only
|
59
|
+
|
60
|
+
EM.add_timer(2) do
|
61
|
+
expect(cp.devices).to be_empty
|
62
|
+
generate_search_responder uuid, 1234
|
63
|
+
EM.add_timer(1) do
|
64
|
+
expect(cp.devices).to be_empty
|
65
|
+
done
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
it '#start should listen for alive notifications' do
|
72
|
+
em do
|
73
|
+
cp.start
|
74
|
+
stub_request(:get, '127.0.0.1:1234/root_description.xml').
|
75
|
+
to_return :headers => {
|
76
|
+
'SERVER' => 'OS/1.0 UPnP/1.1 TEST/1.0'
|
77
|
+
}, :body => generate_xml_device_description(notify_options[:uuid])
|
78
|
+
|
79
|
+
EM.add_timer(2) do
|
80
|
+
expect(cp.devices).to be_empty
|
81
|
+
SSDP.notify :root, :alive, notify_options
|
82
|
+
EM.add_timer(1) do
|
83
|
+
expect(cp.devices).to have(1).item
|
84
|
+
expect(cp.devices[0].udn).to eq(notify_options[:uuid])
|
85
|
+
done
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
it '#start should listen for update notifications' do
|
92
|
+
em do
|
93
|
+
cp.start
|
94
|
+
stub_request(:get, '127.0.0.1:1234/root_description.xml').
|
95
|
+
to_return :headers => {
|
96
|
+
'SERVER' => 'OS/1.0 UPnP/1.1 TEST/1.0'
|
97
|
+
}, :body => generate_xml_device_description(notify_options[:uuid])
|
98
|
+
|
99
|
+
EM.add_timer(2) do
|
100
|
+
expect(cp.devices).to be_empty
|
101
|
+
SSDP.notify :root, :alive, notify_options
|
102
|
+
EM.add_timer(1) do
|
103
|
+
SSDP.notify :root, :update, notify_options.merge(boot_id: 2)
|
104
|
+
EM.add_timer(1) do
|
105
|
+
expect(cp.devices).to have(1).item
|
106
|
+
expect(cp.devices[0].udn).to eq(notify_options[:uuid])
|
107
|
+
expect(cp.devices[0].boot_id).to eq(2)
|
108
|
+
done
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
it '#start should listen for byebye notifications' do
|
116
|
+
em do
|
117
|
+
cp.start
|
118
|
+
stub_request(:get, '127.0.0.1:1234/root_description.xml').
|
119
|
+
to_return :headers => {
|
120
|
+
'SERVER' => 'OS/1.0 UPnP/1.1 TEST/1.0'
|
121
|
+
}, :body => generate_xml_device_description(notify_options[:uuid])
|
122
|
+
|
123
|
+
EM.add_timer(2) do
|
124
|
+
expect(cp.devices).to be_empty
|
125
|
+
SSDP.notify :root, :alive, notify_options
|
126
|
+
EM.add_timer(1) do
|
127
|
+
expect(cp.devices).to have(1).item
|
128
|
+
expect(cp.devices[0].udn).to eq(notify_options[:uuid])
|
129
|
+
SSDP.notify :root, :byebye, notify_options
|
130
|
+
EM.add_timer(1) do
|
131
|
+
expect(cp.devices).to be_empty
|
132
|
+
done
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
it '#find_device_by_udn should get known devices'
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
data/spec/spec_helper.rb
CHANGED
@@ -1,6 +1,14 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start do
|
3
|
+
add_filter "/spec/"
|
4
|
+
end
|
5
|
+
|
1
6
|
$:.unshift '../lib'
|
2
7
|
require 'rupnp'
|
3
8
|
require 'em-spec/rspec'
|
9
|
+
require 'webmock/rspec'
|
10
|
+
|
11
|
+
RUPNP.log_level = :failure
|
4
12
|
|
5
13
|
|
6
14
|
class FakeMulticast < RUPNP::SSDP::MulticastConnection
|
@@ -25,31 +33,80 @@ class FakeMulticast < RUPNP::SSDP::MulticastConnection
|
|
25
33
|
end
|
26
34
|
|
27
35
|
|
36
|
+
def generate_search_responder(uuid, port)
|
37
|
+
responder = EM.open_datagram_socket(RUPNP::MULTICAST_IP,
|
38
|
+
RUPNP::DISCOVERY_PORT,
|
39
|
+
FakeMulticast)
|
40
|
+
responder.onmessage do |data|
|
41
|
+
data =~ /ST: (.*)\r\n/
|
42
|
+
target = $1
|
43
|
+
response =<<EOR
|
44
|
+
HTTP/1.1 200 OK\r
|
45
|
+
CACHE-CONTROL: max-age = 1800\r
|
46
|
+
DATE: #{Time.now}\r
|
47
|
+
EXT:\r
|
48
|
+
LOCATION: http://127.0.0.1:#{port}\r
|
49
|
+
SERVER: OS/1.0 UPnP/1.1 Test/1.0\r
|
50
|
+
ST: #{target}\r
|
51
|
+
USN: uuid:#{uuid}::upnp:rootdevice\r
|
52
|
+
BOOTID.UPNP.ORG: 1\r
|
53
|
+
CONFIGID.UPNP.ORG: 1\r
|
54
|
+
\r
|
55
|
+
EOR
|
56
|
+
responder.send_data response
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
def generate_xml_device_description(uuid)
|
62
|
+
<<EOD
|
63
|
+
<?xml version="1.0"?>
|
64
|
+
<root xmlns="urn:schemas-upnp-org:device-1-0" configId="1">
|
65
|
+
<specVersion>
|
66
|
+
<major>1</major>
|
67
|
+
<minor>1</minor>
|
68
|
+
</specVersion>
|
69
|
+
<device>
|
70
|
+
<deviceType>urn:schemas-upnp-org:device:Base:1-0</deviceType>
|
71
|
+
<friendlyName>Friendly name</friendlyName>
|
72
|
+
<manufacturer>RUPNP</manufacturer>
|
73
|
+
<modelName>Model name</modelName>
|
74
|
+
<UDN>uuid:#{uuid}</UDN>
|
75
|
+
</device>
|
76
|
+
</root>
|
77
|
+
EOD
|
78
|
+
end
|
79
|
+
|
80
|
+
|
28
81
|
NOTIFY_REGEX = {
|
29
|
-
:
|
30
|
-
|
82
|
+
:common => [
|
83
|
+
/^NOTIFY \* HTTP\/1.1\r\n/,
|
84
|
+
/HOST: 239\.255\.255\.250:1900\r\n/,
|
85
|
+
/NT: [0-9A-Za-z:-]+\r\n/,
|
86
|
+
/USN: uuid:(.*)\r\n/,
|
87
|
+
/BOOTID.UPNP.ORG: \d+\r\n/,
|
88
|
+
/CONFIGID.UPNP.ORG: \d+\r\n/,
|
89
|
+
].freeze,
|
90
|
+
:alive => [
|
31
91
|
/CACHE-CONTROL:\s+max-age\s+=\s+\d+\r\n/,
|
32
92
|
/LOCATION: http:\/\/(.*)\r\n/,
|
33
|
-
/
|
34
|
-
/NTS: ssdp:(alive|byebye)\r\n/,
|
93
|
+
/NTS: ssdp:alive\r\n/,
|
35
94
|
/SERVER: (.*)\r\n/,
|
36
|
-
/USN: uuid:(.*)\r\n/,
|
37
|
-
/BOOTID.UPNP.ORG: \d+\r\n/,
|
38
|
-
/CONFIGID.UPNP.ORG: \d+\r\n/,
|
39
|
-
].freeze,
|
40
|
-
:byebye => [/^NOTIFY \* HTTP\/1.1\r\n/,
|
41
|
-
/HOST: 239\.255\.255\.250:1900\r\n/,
|
42
|
-
/NT: [0-9A-Za-z:-]+\r\n/,
|
43
|
-
/NTS: ssdp:(alive|byebye)\r\n/,
|
44
|
-
/USN: uuid:(.*)\r\n/,
|
45
|
-
/BOOTID.UPNP.ORG: \d+\r\n/,
|
46
|
-
/CONFIGID.UPNP.ORG: \d+\r\n/,
|
47
95
|
].freeze,
|
96
|
+
:byebye => [
|
97
|
+
/NTS: ssdp:byebye\r\n/,
|
98
|
+
].freeze,
|
99
|
+
:update => [
|
100
|
+
/LOCATION: http:\/\/(.*)\r\n/,
|
101
|
+
/NTS: ssdp:update\r\n/,
|
102
|
+
/NEXTBOOTID.UPNP.ORG: \d+\r\n/,
|
103
|
+
].freeze
|
48
104
|
}
|
49
105
|
|
50
106
|
RSpec::Matchers.define :be_a_notify_packet do |type|
|
51
107
|
match do |packet|
|
52
|
-
success = NOTIFY_REGEX[
|
108
|
+
success = NOTIFY_REGEX[:common].all? { |r| packet.match(r) }
|
109
|
+
success &&= NOTIFY_REGEX[type].all? { |r| packet.match(r) }
|
53
110
|
success && packet[-4..-1] == "\r\n\r\n"
|
54
111
|
end
|
55
112
|
end
|
@@ -68,3 +125,4 @@ RSpec::Matchers.define :be_a_msearch_packet do
|
|
68
125
|
success && packet[-4..-1] == "\r\n\r\n"
|
69
126
|
end
|
70
127
|
end
|
128
|
+
|
data/spec/ssdp/notifier_spec.rb
CHANGED
data/spec/ssdp/searcher_spec.rb
CHANGED
@@ -47,6 +47,37 @@ module RUPNP
|
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
50
|
+
it "should discard and log bad responses" do
|
51
|
+
reader, writer = IO.pipe
|
52
|
+
begin
|
53
|
+
RUPNP.logdev = writer
|
54
|
+
em do
|
55
|
+
receiver = EM.open_datagram_socket(MULTICAST_IP, DISCOVERY_PORT,
|
56
|
+
FakeMulticast)
|
57
|
+
receiver.onmessage do
|
58
|
+
receiver.send_data "HTTP/1.1 404 Not Found\r\n\r\n"
|
59
|
+
receiver.send_data "this is not an HTTP message!"
|
60
|
+
end
|
61
|
+
searcher = RUPNP::SSDP.search(:all, config.merge(:try_number => 1))
|
62
|
+
searcher.discovery_responses.subscribe do |n|
|
63
|
+
next unless n['server'] =~ /RUPNP/
|
64
|
+
fail
|
65
|
+
end
|
66
|
+
|
67
|
+
EM.add_timer(1) do
|
68
|
+
expect(reader.readline).to match(/\[error\] bad HTTP response/)
|
69
|
+
expect(reader.readline).to match(/404/)
|
70
|
+
reader.readline
|
71
|
+
expect(reader.readline).to match(/\[error\] bad HTTP response/)
|
72
|
+
expect(reader.readline).to match(/not/)
|
73
|
+
done
|
74
|
+
end
|
75
|
+
end
|
76
|
+
ensure
|
77
|
+
reader.close
|
78
|
+
writer.close
|
79
|
+
end
|
80
|
+
end
|
50
81
|
end
|
51
82
|
|
52
83
|
end
|
data/tasks/gem.rake
CHANGED
@@ -19,7 +19,7 @@ EOF
|
|
19
19
|
# For now, device is not in gem.
|
20
20
|
files -= ['lib/rupnp/device.rb', 'spec/device_spec.rb']
|
21
21
|
s.files = files
|
22
|
-
s.executables = "discover"
|
22
|
+
s.executables = ["discover"]
|
23
23
|
|
24
24
|
s.add_dependency 'uuid', '~>2.3.0'
|
25
25
|
s.add_dependency 'eventmachine-le', '~> 1.1.6'
|
@@ -30,6 +30,8 @@ EOF
|
|
30
30
|
|
31
31
|
s.add_development_dependency 'rspec', '~>2.14.7'
|
32
32
|
s.add_development_dependency 'em-spec', '~>0.2.6'
|
33
|
+
s.add_development_dependency 'simplecov', '~>0.8.2'
|
34
|
+
s.add_development_dependency 'webmock', '~>1.16.1'
|
33
35
|
end
|
34
36
|
|
35
37
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rupnp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-01-
|
12
|
+
date: 2014-01-13 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: uuid
|
@@ -139,6 +139,38 @@ dependencies:
|
|
139
139
|
- - ~>
|
140
140
|
- !ruby/object:Gem::Version
|
141
141
|
version: 0.2.6
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: simplecov
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ~>
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: 0.8.2
|
150
|
+
type: :development
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ~>
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: 0.8.2
|
158
|
+
- !ruby/object:Gem::Dependency
|
159
|
+
name: webmock
|
160
|
+
requirement: !ruby/object:Gem::Requirement
|
161
|
+
none: false
|
162
|
+
requirements:
|
163
|
+
- - ~>
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: 1.16.1
|
166
|
+
type: :development
|
167
|
+
prerelease: false
|
168
|
+
version_requirements: !ruby/object:Gem::Requirement
|
169
|
+
none: false
|
170
|
+
requirements:
|
171
|
+
- - ~>
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: 1.16.1
|
142
174
|
description: ! 'RUPNP is a Ruby UPnP framework. For now, only control points (clients)
|
143
175
|
|
144
176
|
are supported. Devices (servers) will be later.
|
@@ -153,6 +185,7 @@ files:
|
|
153
185
|
- spec/ssdp/listener_spec.rb
|
154
186
|
- spec/ssdp/notifier_spec.rb
|
155
187
|
- spec/ssdp/searcher_spec.rb
|
188
|
+
- spec/control_point_spec.rb
|
156
189
|
- spec/spec_helper.rb
|
157
190
|
- lib/rupnp.rb
|
158
191
|
- lib/rupnp/constants.rb
|