playful 0.1.0.alpha.1
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.
- checksums.yaml +7 -0
- data/.gemtest +0 -0
- data/.gitignore +19 -0
- data/.rspec +1 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/History.rdoc +3 -0
- data/LICENSE.rdoc +22 -0
- data/README.rdoc +194 -0
- data/Rakefile +20 -0
- data/features/control_point.feature +13 -0
- data/features/device.feature +22 -0
- data/features/device_discovery.feature +9 -0
- data/features/step_definitions/control_point_steps.rb +19 -0
- data/features/step_definitions/device_discovery_steps.rb +40 -0
- data/features/step_definitions/device_steps.rb +28 -0
- data/features/support/common.rb +9 -0
- data/features/support/env.rb +17 -0
- data/features/support/fake_upnp_device_collection.rb +108 -0
- data/features/support/world_extensions.rb +15 -0
- data/lib/core_ext/hash_patch.rb +5 -0
- data/lib/core_ext/socket_patch.rb +16 -0
- data/lib/core_ext/to_upnp_s.rb +65 -0
- data/lib/playful.rb +5 -0
- data/lib/playful/control_point.rb +175 -0
- data/lib/playful/control_point/base.rb +74 -0
- data/lib/playful/control_point/device.rb +511 -0
- data/lib/playful/control_point/error.rb +13 -0
- data/lib/playful/control_point/service.rb +404 -0
- data/lib/playful/device.rb +28 -0
- data/lib/playful/logger.rb +8 -0
- data/lib/playful/ssdp.rb +195 -0
- data/lib/playful/ssdp/broadcast_searcher.rb +114 -0
- data/lib/playful/ssdp/error.rb +6 -0
- data/lib/playful/ssdp/listener.rb +38 -0
- data/lib/playful/ssdp/multicast_connection.rb +112 -0
- data/lib/playful/ssdp/network_constants.rb +17 -0
- data/lib/playful/ssdp/notifier.rb +41 -0
- data/lib/playful/ssdp/searcher.rb +87 -0
- data/lib/playful/version.rb +3 -0
- data/lib/rack/upnp_control_point.rb +70 -0
- data/playful.gemspec +38 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/support/search_responses.rb +134 -0
- data/spec/unit/core_ext/to_upnp_s_spec.rb +105 -0
- data/spec/unit/playful/control_point/device_spec.rb +7 -0
- data/spec/unit/playful/control_point_spec.rb +45 -0
- data/spec/unit/playful/ssdp/listener_spec.rb +29 -0
- data/spec/unit/playful/ssdp/multicast_connection_spec.rb +157 -0
- data/spec/unit/playful/ssdp/notifier_spec.rb +76 -0
- data/spec/unit/playful/ssdp/searcher_spec.rb +110 -0
- data/spec/unit/playful/ssdp_spec.rb +214 -0
- data/tasks/control_point.html +30 -0
- data/tasks/control_point.thor +43 -0
- data/tasks/search.thor +128 -0
- data/tasks/test_js/FABridge.js +1425 -0
- data/tasks/test_js/WebSocketMain.swf +807 -0
- data/tasks/test_js/swfobject.js +825 -0
- data/tasks/test_js/web_socket.js +1133 -0
- data/test/test_ssdp.rb +298 -0
- data/test/test_ssdp_notification.rb +74 -0
- data/test/test_ssdp_response.rb +31 -0
- data/test/test_ssdp_search.rb +23 -0
- metadata +339 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
module Playful
|
2
|
+
class SSDP
|
3
|
+
module NetworkConstants
|
4
|
+
|
5
|
+
BROADCAST_IP = '255.255.255.255'
|
6
|
+
|
7
|
+
# Default multicast IP address
|
8
|
+
MULTICAST_IP = '239.255.255.250'
|
9
|
+
|
10
|
+
# Default multicast port
|
11
|
+
MULTICAST_PORT = 1900
|
12
|
+
|
13
|
+
# Default TTL
|
14
|
+
TTL = 4
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require_relative '../logger'
|
2
|
+
require_relative 'multicast_connection'
|
3
|
+
|
4
|
+
|
5
|
+
class Playful::SSDP::Notifier < Playful::SSDP::MulticastConnection
|
6
|
+
include LogSwitch::Mixin
|
7
|
+
|
8
|
+
def initialize(nt, usn, ddf_url, valid_for_duration)
|
9
|
+
@os = RbConfig::CONFIG['host_vendor'].capitalize + '/' +
|
10
|
+
RbConfig::CONFIG['host_os']
|
11
|
+
@upnp_version = '1.0'
|
12
|
+
@notification = notification(nt, usn, ddf_url, valid_for_duration)
|
13
|
+
end
|
14
|
+
|
15
|
+
def post_init
|
16
|
+
if send_datagram(@notification, MULTICAST_IP, MULTICAST_PORT) > 0
|
17
|
+
log "Sent notification:\n#{@notification}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# @param [String] nt "Notification Type"; a potential search target. Used in
|
22
|
+
# +NT+ header.
|
23
|
+
# @param [String] usn "Unique Service Name"; a composite identifier for the
|
24
|
+
# advertisement. Used in +USN+ header.
|
25
|
+
# @param [String] ddf_url Device Description File URL for the root device.
|
26
|
+
# @param [Fixnum] valid_for_duration Duration in seconds for which the
|
27
|
+
# advertisement is valid. Used in +CACHE-CONTROL+ header.
|
28
|
+
def notification(nt, usn, ddf_url, valid_for_duration)
|
29
|
+
<<-NOTIFICATION
|
30
|
+
NOTIFY * HTTP/1.1\r
|
31
|
+
HOST: #{MULTICAST_IP}:#{MULTICAST_PORT}\r
|
32
|
+
CACHE-CONTROL: max-age=#{valid_for_duration}\r
|
33
|
+
LOCATION: #{ddf_url}\r
|
34
|
+
NT: #{nt}\r
|
35
|
+
NTS: ssdp:alive\r
|
36
|
+
SERVER: #{@os} UPnP/#{@upnp_version} Playful/#{Playful::VERSION}\r
|
37
|
+
USN: #{usn}\r
|
38
|
+
\r
|
39
|
+
NOTIFICATION
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require_relative '../logger'
|
2
|
+
require_relative 'multicast_connection'
|
3
|
+
|
4
|
+
module Playful
|
5
|
+
|
6
|
+
# A subclass of an EventMachine::Connection, this handles doing M-SEARCHes.
|
7
|
+
#
|
8
|
+
# Search types:
|
9
|
+
# ssdp:all
|
10
|
+
# upnp:rootdevice
|
11
|
+
# uuid:[device-uuid]
|
12
|
+
# urn:schemas-upnp-org:device:[deviceType-version]
|
13
|
+
# urn:schemas-upnp-org:service:[serviceType-version]
|
14
|
+
# urn:[custom-schema]:device:[deviceType-version]
|
15
|
+
# urn:[custom-schema]:service:[serviceType-version]
|
16
|
+
class SSDP::Searcher < SSDP::MulticastConnection
|
17
|
+
include LogSwitch::Mixin
|
18
|
+
|
19
|
+
DEFAULT_RESPONSE_WAIT_TIME = 5
|
20
|
+
DEFAULT_M_SEARCH_COUNT = 2
|
21
|
+
|
22
|
+
# @return [EventMachine::Channel] Provides subscribers with responses from
|
23
|
+
# their search request.
|
24
|
+
attr_reader :discovery_responses
|
25
|
+
|
26
|
+
# @param [String] search_target
|
27
|
+
# @param [Hash] options
|
28
|
+
# @option options [Fixnum] response_wait_time
|
29
|
+
# @option options [Fixnum] ttl
|
30
|
+
# @option options [Fixnum] m_search_count The number of times to send the
|
31
|
+
# M-SEARCH. UPnP 1.0 suggests to send the request more than once.
|
32
|
+
def initialize(search_target, options = {})
|
33
|
+
options[:ttl] ||= TTL
|
34
|
+
options[:response_wait_time] ||= DEFAULT_RESPONSE_WAIT_TIME
|
35
|
+
@m_search_count = options[:m_search_count] ||= DEFAULT_M_SEARCH_COUNT
|
36
|
+
|
37
|
+
@search = m_search(search_target, options[:response_wait_time])
|
38
|
+
|
39
|
+
super options[:ttl]
|
40
|
+
end
|
41
|
+
|
42
|
+
# This is the callback called by EventMachine when it receives data on the
|
43
|
+
# socket that's been opened for this connection. In this case, the method
|
44
|
+
# parses the SSDP responses/notifications into Hashes and adds them to the
|
45
|
+
# appropriate EventMachine::Channel (provided as accessor methods). This
|
46
|
+
# effectively means that in each Channel, you get a Hash that represents
|
47
|
+
# the headers for each response/notification that comes in on the socket.
|
48
|
+
#
|
49
|
+
# @param [String] response The data received on this connection's socket.
|
50
|
+
def receive_data(response)
|
51
|
+
ip, port = peer_info
|
52
|
+
log "Response from #{ip}:#{port}:\n#{response}\n"
|
53
|
+
parsed_response = parse(response)
|
54
|
+
|
55
|
+
return if parsed_response.has_key? :nts
|
56
|
+
return if parsed_response[:man] &&
|
57
|
+
parsed_response[:man] =~ /ssdp:discover/
|
58
|
+
|
59
|
+
@discovery_responses << parsed_response
|
60
|
+
end
|
61
|
+
|
62
|
+
# Sends the M-SEARCH that was built during init. Logs what was sent if the
|
63
|
+
# send was successful.
|
64
|
+
def post_init
|
65
|
+
@m_search_count.times do
|
66
|
+
if send_datagram(@search, MULTICAST_IP, MULTICAST_PORT) > 0
|
67
|
+
log "Sent datagram search:\n#{@search}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Builds the M-SEARCH request string.
|
73
|
+
#
|
74
|
+
# @param [String] search_target
|
75
|
+
# @param [Fixnum] response_wait_time
|
76
|
+
def m_search(search_target, response_wait_time)
|
77
|
+
<<-MSEARCH
|
78
|
+
M-SEARCH * HTTP/1.1\r
|
79
|
+
HOST: #{MULTICAST_IP}:#{MULTICAST_PORT}\r
|
80
|
+
MAN: "ssdp:discover"\r
|
81
|
+
MX: #{response_wait_time}\r
|
82
|
+
ST: #{search_target}\r
|
83
|
+
\r
|
84
|
+
MSEARCH
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require_relative '../playful/control_point'
|
3
|
+
|
4
|
+
module Rack
|
5
|
+
|
6
|
+
# Middleware that allows your Rack app to keep tabs on devices that the
|
7
|
+
# {Playful::ControlPoint} has found. UPnP devices that match the +search_type+
|
8
|
+
# are discovered and added to the list, then removed as those devices send out
|
9
|
+
# +ssdp:byebye+ notifications. All of this depends on +EventMachine::Channel+s,
|
10
|
+
# and thus requires that an EventMachine reactor is running. If you don't
|
11
|
+
# have one running, the {Playful::ControlPoint} will start one for you.
|
12
|
+
#
|
13
|
+
# @example Control all root devices
|
14
|
+
#
|
15
|
+
# Thin::Server.start('0.0.0.0', 3000) do
|
16
|
+
# use Rack::UPnPControlPoint, search_type: :root
|
17
|
+
#
|
18
|
+
# map "/devices" do
|
19
|
+
# run lambda { |env|
|
20
|
+
# devices = env['upnp.devices']
|
21
|
+
# friendly_names = devices.map(&:friendly_name).join("\n")
|
22
|
+
# [200, {'Content-Type' => 'text/plain'}, [friendly_names]]
|
23
|
+
# }
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
class UPnPControlPoint
|
28
|
+
|
29
|
+
# @param [Rack::Builder] app Your Rack application.
|
30
|
+
# @param [Hash] options Options to pass to the Playful::SSDP::Searcher.
|
31
|
+
# @see Playful::SSDP::Searcher
|
32
|
+
def initialize(app, options = {})
|
33
|
+
@app = app
|
34
|
+
@devices = []
|
35
|
+
options[:search_type] ||= :root
|
36
|
+
EM.next_tick { start_control_point(options[:search_type], options) }
|
37
|
+
end
|
38
|
+
|
39
|
+
# Creates and starts the {Playful::ControlPoint}, then manages the list of
|
40
|
+
# devices using the +EventMachine::Channel+ objects yielded in.
|
41
|
+
#
|
42
|
+
# @param [Symbol,String] search_type The device(s) you want to search for
|
43
|
+
# and control.
|
44
|
+
# @param [Hash] options Options to pass to the Playful::SSDP::Searcher.
|
45
|
+
# @see Playful::SSDP::Searcher
|
46
|
+
def start_control_point(search_type, options)
|
47
|
+
@cp = ::Playful::ControlPoint.new(search_type, options)
|
48
|
+
|
49
|
+
@cp.start do |new_device_channel, old_device_channel|
|
50
|
+
new_device_channel.subscribe do |notification|
|
51
|
+
@devices << notification
|
52
|
+
end
|
53
|
+
|
54
|
+
old_device_channel.subscribe do |old_device|
|
55
|
+
@devices.reject! { |d| d.usn == old_device[:usn] }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
# Adds the whole list of devices to <tt>env['upnp.devices']</tt> so that
|
62
|
+
# that list can be accessed from within your app.
|
63
|
+
#
|
64
|
+
# @param [Hash] env The Rack environment.
|
65
|
+
def call(env)
|
66
|
+
env['upnp.devices'] = @devices
|
67
|
+
@app.call(env)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
data/playful.gemspec
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path('../lib', __FILE__)
|
3
|
+
require 'playful/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'playful'
|
7
|
+
s.version = Playful::VERSION
|
8
|
+
s.author = 'turboladen'
|
9
|
+
s.email = 'steve.loveless@gmail.com'
|
10
|
+
s.homepage = 'http://github.com/turboladen/playful'
|
11
|
+
s.summary = 'Use me to build a UPnP app!'
|
12
|
+
s.description = %q{playful provides the tools you need to build an app that runs
|
13
|
+
in a UPnP environment.}
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {spec,features}/*`.split("\n")
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
|
+
s.extra_rdoc_files = %w(History.rdoc README.rdoc)
|
19
|
+
s.require_paths = ['lib']
|
20
|
+
s.required_ruby_version = Gem::Requirement.new('>=1.9.1')
|
21
|
+
|
22
|
+
s.add_dependency 'eventmachine', '>=1.0.0'
|
23
|
+
s.add_dependency 'em-http-request', '>=1.0.2'
|
24
|
+
s.add_dependency 'em-synchrony'
|
25
|
+
s.add_dependency 'nori', '>=2.0.2'
|
26
|
+
s.add_dependency 'log_switch', '>=0.4.0'
|
27
|
+
s.add_dependency 'savon', '~>2.0'
|
28
|
+
|
29
|
+
s.add_development_dependency 'bundler'
|
30
|
+
s.add_development_dependency 'cucumber', '>=1.0.0'
|
31
|
+
s.add_development_dependency 'em-websocket', '>=0.3.6'
|
32
|
+
s.add_development_dependency 'rake'
|
33
|
+
s.add_development_dependency 'rspec', '>=3.0.0.beta'
|
34
|
+
s.add_development_dependency 'simplecov', '>=0.4.2'
|
35
|
+
s.add_development_dependency 'thin'
|
36
|
+
s.add_development_dependency 'thor', '>=0.1.6'
|
37
|
+
s.add_development_dependency 'yard', '>=0.7.0'
|
38
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
|
3
|
+
SimpleCov.start
|
4
|
+
|
5
|
+
require 'coveralls'
|
6
|
+
Coveralls.wear!
|
7
|
+
|
8
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
9
|
+
|
10
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |file| require file }
|
11
|
+
|
12
|
+
ENV['RUBY_UPNP_ENV'] = 'testing'
|
13
|
+
|
14
|
+
RSpec.configure do |config|
|
15
|
+
#config.raise_errors_for_deprecations!
|
16
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
SSDP_SEARCH_RESPONSES_PARSED = {
|
2
|
+
root_device1: {
|
3
|
+
cache_control: 'max-age=1200',
|
4
|
+
date: 'Mon, 26 Sep 2011 06:40:19 GMT',
|
5
|
+
location: 'http://192.168.10.3:5001/description/fetch',
|
6
|
+
server: 'Linux-i386-2.6.38-10-generic-pae, UPnP/1.0, PMS/1.25.1',
|
7
|
+
st: 'upnp:rootdevice',
|
8
|
+
ext: nil,
|
9
|
+
usn: 'uuid:3c202906-992d-3f0f-b94c-90e1902a136d::upnp:rootdevice',
|
10
|
+
content_length: 0
|
11
|
+
},
|
12
|
+
root_device2: {
|
13
|
+
cache_control: 'max-age=1200',
|
14
|
+
date: 'Mon, 26 Sep 2011 06:40:20 GMT',
|
15
|
+
location: 'http://192.168.10.4:5001/description/fetch',
|
16
|
+
server: 'Linux-i386-2.6.38-10-generic-pae, UPnP/1.0, PMS/1.25.1',
|
17
|
+
st: 'upnp:rootdevice',
|
18
|
+
ext: nil,
|
19
|
+
usn: 'uuid:3c202906-992d-3f0f-b94c-90e1902a136e::upnp:rootdevice',
|
20
|
+
content_length: 0
|
21
|
+
},
|
22
|
+
media_server: {
|
23
|
+
cache_control: 'max-age=1200',
|
24
|
+
date: 'Mon, 26 Sep 2011 06:40:21 GMT',
|
25
|
+
location: 'http://192.168.10.3:5001/description/fetch',
|
26
|
+
server: 'Linux-i386-2.6.38-10-generic-pae, UPnP/1.0, PMS/1.25.1',
|
27
|
+
st: 'urn:schemas-upnp-org:device:MediaServer:1',
|
28
|
+
ext: nil,
|
29
|
+
usn: 'uuid:3c202906-992d-3f0f-b94c-90e1902a136d::urn:schemas-upnp-org:device:MediaServer:1',
|
30
|
+
content_length: 0
|
31
|
+
}
|
32
|
+
}
|
33
|
+
|
34
|
+
SSDP_DESCRIPTIONS = {
|
35
|
+
root_device1: {
|
36
|
+
'root' => {
|
37
|
+
'specVersion' => {
|
38
|
+
'major' => '1',
|
39
|
+
'minor' => '0'
|
40
|
+
},
|
41
|
+
'URLBase' => 'http://192.168.10.3:5001/',
|
42
|
+
'device' => {
|
43
|
+
'dlna:X_DLNADOC' => %w[DMS-1.50 M-DMS-1.50],
|
44
|
+
'deviceType' => 'urn:schemas-upnp-org:device:MediaServer:1',
|
45
|
+
'friendlyName' => 'PS3 Media Server [gutenberg]',
|
46
|
+
'manufacturer' => 'PMS',
|
47
|
+
'manufacturerURL' => 'http://ps3mediaserver.blogspot.com',
|
48
|
+
'modelDescription' => 'UPnP/AV 1.0 Compliant Media Server',
|
49
|
+
'modelName' => 'PMS',
|
50
|
+
'modelNumber' => '01',
|
51
|
+
'modelURL' => 'http://ps3mediaserver.blogspot.com',
|
52
|
+
'serialNumber' => nil,
|
53
|
+
'UPC' => nil,
|
54
|
+
'UDN' => 'uuid:3c202906-992d-3f0f-b94c-90e1902a136d',
|
55
|
+
'iconList' => {
|
56
|
+
'icon' => {
|
57
|
+
'mimetype' => 'image/jpeg',
|
58
|
+
'width' => '120',
|
59
|
+
'height' => '120',
|
60
|
+
'depth' => '24',
|
61
|
+
'url' => '/images/icon-256.png'
|
62
|
+
}
|
63
|
+
},
|
64
|
+
'presentationURL' => 'http://192.168.10.3:5001/console/index.html',
|
65
|
+
'serviceList' => {
|
66
|
+
'service' => [
|
67
|
+
{
|
68
|
+
'serviceType' => 'urn:schemas-upnp-org:service:ContentDirectory:1',
|
69
|
+
'serviceId' => 'urn:upnp-org:serviceId:ContentDirectory',
|
70
|
+
'SCPDURL' => '/UPnP_AV_ContentDirectory_1.0.xml',
|
71
|
+
'controlURL' => '/upnp/control/content_directory',
|
72
|
+
'eventSubURL' => '/upnp/event/content_directory'
|
73
|
+
},
|
74
|
+
{
|
75
|
+
'serviceType' => 'urn:schemas-upnp-org:service:ConnectionManager:1',
|
76
|
+
'serviceId' => 'urn:upnp-org:serviceId:ConnectionManager',
|
77
|
+
'SCPDURL' => '/UPnP_AV_ConnectionManager_1.0.xml',
|
78
|
+
'controlURL' => '/upnp/control/connection_manager',
|
79
|
+
'eventSubURL' => '/upnp/event/connection_manager'
|
80
|
+
}
|
81
|
+
]
|
82
|
+
}
|
83
|
+
},
|
84
|
+
'@xmlns:dlna' => 'urn:schemas-dlna-org:device-1-0',
|
85
|
+
'@xmlns' => 'urn:schemas-upnp-org:device-1-0'
|
86
|
+
}
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
ROOT_DEVICE1 = <<-RD
|
91
|
+
HTTP/1.1 200 OK
|
92
|
+
CACHE-CONTROL: max-age=1200
|
93
|
+
DATE: Mon, 26 Sep 2011 06:40:19 GMT
|
94
|
+
LOCATION: http://1.2.3.4:5678/description/fetch
|
95
|
+
SERVER: Linux-i386-2.6.38-10-generic-pae, UPnP/1.0, PMS/1.25.1
|
96
|
+
ST: upnp:rootdevice
|
97
|
+
EXT:
|
98
|
+
USN: uuid:3c202906-992d-3f0f-b94c-90e1902a136d::upnp:rootdevice
|
99
|
+
Content-Length: 0
|
100
|
+
|
101
|
+
RD
|
102
|
+
|
103
|
+
ROOT_DEVICE2 = <<-RD
|
104
|
+
HTTP/1.1 200 OK
|
105
|
+
CACHE-CONTROL: max-age=1200
|
106
|
+
DATE: Mon, 26 Sep 2011 06:40:20 GMT
|
107
|
+
LOCATION: http://1.2.3.4:5678/description/fetch
|
108
|
+
SERVER: Linux-i386-2.6.38-10-generic-pae, UPnP/1.0, PMS/1.25.1
|
109
|
+
ST: upnp:rootdevice
|
110
|
+
EXT:
|
111
|
+
USN: uuid:3c202906-992d-3f0f-b94c-90e1902a136e::upnp:rootdevice
|
112
|
+
Content-Length: 0
|
113
|
+
|
114
|
+
RD
|
115
|
+
|
116
|
+
MEDIA_SERVER = <<-MD
|
117
|
+
HTTP/1.1 200 OK
|
118
|
+
CACHE-CONTROL: max-age=1200
|
119
|
+
DATE: Mon, 26 Sep 2011 06:40:21 GMT
|
120
|
+
LOCATION: http://1.2.3.4:5678/description/fetch
|
121
|
+
SERVER: Linux-i386-2.6.38-10-generic-pae, UPnP/1.0, PMS/1.25.1
|
122
|
+
ST: urn:schemas-upnp-org:device:MediaServer:1
|
123
|
+
EXT:
|
124
|
+
USN: uuid:3c202906-992d-3f0f-b94c-90e1902a136d::urn:schemas-upnp-org:device:MediaServer:
|
125
|
+
Content-Length: 0
|
126
|
+
|
127
|
+
MD
|
128
|
+
|
129
|
+
RESPONSES = {
|
130
|
+
root_device1: ROOT_DEVICE1,
|
131
|
+
root_device2: ROOT_DEVICE2,
|
132
|
+
media_server: MEDIA_SERVER
|
133
|
+
}
|
134
|
+
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'core_ext/to_upnp_s'
|
3
|
+
|
4
|
+
|
5
|
+
describe Hash do
|
6
|
+
describe '#to_upnp_s' do
|
7
|
+
context ':uuid as key' do
|
8
|
+
it "returns a String like 'uuid:[Hash value]'" do
|
9
|
+
expect({ uuid: '12345' }.to_upnp_s).to eq 'uuid:12345'
|
10
|
+
end
|
11
|
+
|
12
|
+
it "doesn't check if the Hash value is legit" do
|
13
|
+
expect({ uuid: '' }.to_upnp_s).to eq 'uuid:'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context ':device_type as key' do
|
18
|
+
context 'domain name not given' do
|
19
|
+
it "returns a String like 'urn:schemas-upnp-org:device:[device type]'" do
|
20
|
+
expect({ device_type: '12345' }.to_upnp_s).
|
21
|
+
to eq 'urn:schemas-upnp-org:device:12345'
|
22
|
+
end
|
23
|
+
|
24
|
+
it "doesn't check if the Hash value is legit" do
|
25
|
+
expect({ device_type: '' }.to_upnp_s).to eq 'urn:schemas-upnp-org:device:'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'domain name given' do
|
30
|
+
it "returns a String like 'urn:[domain name]:device:[device type]'" do
|
31
|
+
expect({ device_type: '12345', domain_name: 'my-domain' }.to_upnp_s).
|
32
|
+
to eq 'urn:my-domain:device:12345'
|
33
|
+
end
|
34
|
+
|
35
|
+
it "doesn't check if the Hash value is legit" do
|
36
|
+
expect({ device_type: '', domain_name: 'stuff' }.to_upnp_s).
|
37
|
+
to eq 'urn:stuff:device:'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context ':service_type as key' do
|
43
|
+
context 'domain name not given' do
|
44
|
+
it "returns a String like 'urn:schemas-upnp-org:service:[service type]'" do
|
45
|
+
expect({ service_type: '12345' }.to_upnp_s).
|
46
|
+
to eq 'urn:schemas-upnp-org:service:12345'
|
47
|
+
end
|
48
|
+
|
49
|
+
it "doesn't check if the Hash value is legit" do
|
50
|
+
expect({ service_type: '' }.to_upnp_s).to eq 'urn:schemas-upnp-org:service:'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'domain name given' do
|
55
|
+
it "returns a String like 'urn:[domain name]:service:[service type]'" do
|
56
|
+
expect({ service_type: '12345', domain_name: 'my-domain' }.to_upnp_s).
|
57
|
+
to eq 'urn:my-domain:service:12345'
|
58
|
+
end
|
59
|
+
|
60
|
+
it "doesn't check if the Hash value is legit" do
|
61
|
+
expect({ service_type: '', domain_name: 'my-domain' }.to_upnp_s).
|
62
|
+
to eq 'urn:my-domain:service:'
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'some other Hash key' do
|
68
|
+
context 'domain name not given' do
|
69
|
+
it 'returns self.to_s' do
|
70
|
+
expect({ firestorm: 12345 }.to_upnp_s).to eq '{:firestorm=>12345}'
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe Symbol do
|
78
|
+
context ':all' do
|
79
|
+
describe '#to_upnp_s' do
|
80
|
+
it "returns 'ssdp:all'" do
|
81
|
+
expect(:all.to_upnp_s).to eq 'ssdp:all'
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context ':root' do
|
87
|
+
describe '#to_upnp_s' do
|
88
|
+
it "returns 'upnp:rootdevice'" do
|
89
|
+
expect(:root.to_upnp_s).to eq 'upnp:rootdevice'
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
it "returns itself if one of the defined shortcuts wasn't given" do
|
95
|
+
expect(:firestorm.to_upnp_s).to eq :firestorm
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe String do
|
100
|
+
it 'returns itself' do
|
101
|
+
expect('Stuff and things'.to_upnp_s).to eq 'Stuff and things'
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
|