frisky 0.1.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.
@@ -0,0 +1,76 @@
1
+ require 'spec_helper'
2
+ require 'frisky/ssdp/notifier'
3
+
4
+
5
+ describe Frisky::SSDP::Notifier do
6
+ around(:each) do |example|
7
+ EM.synchrony do
8
+ example.run
9
+ EM.stop
10
+ end
11
+ end
12
+
13
+ let(:nt) { 'en tee' }
14
+ let(:usn) { 'you ess en' }
15
+ let(:ddf_url) { 'ddf url' }
16
+ let(:duration) { 567 }
17
+
18
+ subject do
19
+ Frisky::SSDP::Notifier.new(1, nt, usn, ddf_url, duration)
20
+ end
21
+
22
+ describe '#initialize' do
23
+ it 'creates a notification' do
24
+ expect_any_instance_of(Frisky::SSDP::Notifier).to receive(:notification).
25
+ with(nt, usn, ddf_url, duration)
26
+
27
+ subject
28
+ end
29
+ end
30
+
31
+ describe '#post_init' do
32
+ context 'send_datagram returns positive value' do
33
+ before do
34
+ expect(subject).to receive(:send_datagram).and_return 1
35
+ end
36
+
37
+ it 'logs what was sent' do
38
+ expect(subject).to receive(:log).with /Sent notification/
39
+
40
+ subject.post_init
41
+ end
42
+ end
43
+
44
+ context 'send_datagram returns 0' do
45
+ before do
46
+ expect(subject).to receive(:send_datagram).and_return 0
47
+ end
48
+
49
+ it 'does not log what was sent' do
50
+ expect(subject).to_not receive(:log)
51
+
52
+ subject.post_init
53
+ end
54
+ end
55
+ end
56
+
57
+ describe '#notification' do
58
+ before do
59
+ subject.instance_variable_set(:@os, 'my OS')
60
+ end
61
+
62
+ it 'builds the notification message' do
63
+ expect(subject.notification(nt, usn, ddf_url, duration)).to eq <<-NOTE
64
+ NOTIFY * HTTP/1.1\r
65
+ HOST: 239.255.255.250:1900\r
66
+ CACHE-CONTROL: max-age=567\r
67
+ LOCATION: ddf url\r
68
+ NT: en tee\r
69
+ NTS: ssdp:alive\r
70
+ SERVER: my OS UPnP/1.0 Frisky/#{Frisky::VERSION}\r
71
+ USN: you ess en\r
72
+ \r
73
+ NOTE
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,110 @@
1
+ require 'spec_helper'
2
+ require 'frisky/ssdp/searcher'
3
+
4
+
5
+ describe Frisky::SSDP::Searcher do
6
+ around(:each) do |example|
7
+ EM.synchrony do
8
+ example.run
9
+ EM.stop
10
+ end
11
+ end
12
+
13
+ before do
14
+ Frisky.logging_enabled = false
15
+ allow_any_instance_of(Frisky::SSDP::MulticastConnection).to receive(:setup_multicast_socket)
16
+ end
17
+
18
+ subject do
19
+ Frisky::SSDP::Searcher.new(1, 'ssdp:all', {})
20
+ end
21
+
22
+ it 'lets you read its responses' do
23
+ responses = double 'responses'
24
+ subject.instance_variable_set(:@discovery_responses, responses)
25
+ expect(subject.discovery_responses).to eq responses
26
+ end
27
+
28
+ describe '#initialize' do
29
+ it 'does an #m_search' do
30
+ expect_any_instance_of(Frisky::SSDP::Searcher).to receive(:m_search).and_return(<<-MSEARCH
31
+ M-SEARCH * HTTP/1.1\r
32
+ HOST: 239.255.255.250:1900\r
33
+ MAN: "ssdp:discover"\r
34
+ MX: 5\r
35
+ ST: ssdp:all\r
36
+ \r
37
+ MSEARCH
38
+ )
39
+ subject
40
+ end
41
+ end
42
+
43
+ describe '#receive_data' do
44
+ let(:parsed_response) do
45
+ parsed_response = double 'parsed response'
46
+ expect(parsed_response).to receive(:has_key?).with(:nts).and_return false
47
+ expect(parsed_response).to receive(:[]).and_return false
48
+
49
+ parsed_response
50
+ end
51
+
52
+ it 'takes a response and adds it to the list of responses' do
53
+ response = double 'response'
54
+ allow(subject).to receive(:peer_info).and_return(['0.0.0.0', 4567])
55
+
56
+ expect(subject).to receive(:parse).with(response).exactly(1).times.
57
+ and_return(parsed_response)
58
+ expect(subject.instance_variable_get(:@discovery_responses)).to receive(:<<).
59
+ with(parsed_response)
60
+
61
+ subject.receive_data(response)
62
+ end
63
+ end
64
+
65
+ describe '#post_init' do
66
+ before { allow_any_instance_of(Frisky::SSDP::Searcher).to receive(:m_search).and_return('hi') }
67
+
68
+ it 'sends an M-SEARCH as a datagram over 239.255.255.250:1900' do
69
+ m_search_count_times = subject.instance_variable_get(:@m_search_count)
70
+ expect(subject).to receive(:send_datagram).
71
+ with('hi', '239.255.255.250', 1900).
72
+ exactly(m_search_count_times).times.
73
+ and_return 0
74
+ subject.post_init
75
+ end
76
+ end
77
+
78
+ describe '#m_search' do
79
+ it 'builds the MSEARCH string using the given parameters' do
80
+ expect(subject.m_search('ssdp:all', 10)).to eq <<-MSEARCH
81
+ M-SEARCH * HTTP/1.1\r
82
+ HOST: 239.255.255.250:1900\r
83
+ MAN: "ssdp:discover"\r
84
+ MX: 10\r
85
+ ST: ssdp:all\r
86
+ \r
87
+ MSEARCH
88
+ end
89
+
90
+ it 'uses 239.255.255.250 as the HOST IP' do
91
+ expect(subject.m_search('ssdp:all', 10)).to match(/HOST: 239.255.255.250/m)
92
+ end
93
+
94
+ it 'uses 1900 as the HOST port' do
95
+ expect(subject.m_search('ssdp:all', 10)).to match(/HOST:.*1900/m)
96
+ end
97
+
98
+ it 'lets you search for undefined search target types' do
99
+ expect(subject.m_search('spaceship', 10)).to eq <<-MSEARCH
100
+ M-SEARCH * HTTP/1.1\r
101
+ HOST: 239.255.255.250:1900\r
102
+ MAN: "ssdp:discover"\r
103
+ MX: 10\r
104
+ ST: spaceship\r
105
+ \r
106
+ MSEARCH
107
+ end
108
+ end
109
+ end
110
+
@@ -0,0 +1,214 @@
1
+ require 'spec_helper'
2
+ require 'frisky/ssdp'
3
+
4
+
5
+ describe Frisky::SSDP do
6
+ subject { Frisky::SSDP }
7
+
8
+ describe '.listen' do
9
+ let(:listener) do
10
+ searcher = double 'Frisky::SSDP::Listener'
11
+ searcher.stub_chain(:alive_notifications, :pop).and_yield(%w[one two])
12
+ searcher.stub_chain(:byebye_notifications, :pop).and_yield(%w[three four])
13
+
14
+ searcher
15
+ end
16
+
17
+ before do
18
+ allow(EM).to receive(:run).and_yield
19
+ allow(EM).to receive(:add_timer)
20
+ allow(EM).to receive(:open_datagram_socket).and_return listener
21
+ end
22
+
23
+ context 'reactor is already running' do
24
+ it 'returns a Frisky::SSDP::Listener' do
25
+ allow(EM).to receive(:reactor_running?).and_return true
26
+ expect(subject.listen).to eq listener
27
+ end
28
+ end
29
+
30
+ context 'reactor is not already running' do
31
+ it 'returns a Hash of available and byebye responses' do
32
+ allow(EM).to receive(:add_shutdown_hook).and_yield
33
+ expect(subject.listen).to eq({
34
+ alive_notifications: %w[one two],
35
+ byebye_notifications: %w[three four]
36
+ })
37
+ end
38
+
39
+ it 'opens a UDP socket on 239.255.255.250, port 1900' do
40
+ allow(EM).to receive(:add_shutdown_hook)
41
+ expect(EM).to receive(:open_datagram_socket).with('239.255.255.250', 1900,
42
+ Frisky::SSDP::Listener, 4)
43
+ subject.listen
44
+ end
45
+ end
46
+ end
47
+
48
+ describe '.search' do
49
+ let(:multicast_searcher) do
50
+ searcher = double 'Frisky::SSDP::Searcher'
51
+ searcher.stub_chain(:discovery_responses, :subscribe).and_yield(%w[one two])
52
+
53
+ searcher
54
+ end
55
+
56
+ let(:broadcast_searcher) do
57
+ searcher = double 'Frisky::SSDP::BroadcastSearcher'
58
+ searcher.stub_chain(:discovery_responses, :subscribe).and_yield(%w[three four])
59
+
60
+ searcher
61
+ end
62
+
63
+ before do
64
+ allow(EM).to receive(:run).and_yield
65
+ allow(EM).to receive(:add_timer)
66
+ allow(EM).to receive(:open_datagram_socket).and_return multicast_searcher
67
+ end
68
+
69
+ context 'when search_target is not a String' do
70
+ it 'calls #to_upnp_s on search_target' do
71
+ search_target = double('search_target')
72
+ expect(search_target).to receive(:to_upnp_s)
73
+ subject.search(search_target)
74
+ end
75
+ end
76
+
77
+ context 'when search_target is a String' do
78
+ it 'calls #to_upnp_s on search_target but does not alter it' do
79
+ search_target = "I'm a string"
80
+ expect(search_target).to receive(:to_upnp_s).and_call_original
81
+
82
+ expect(EM).to receive(:open_datagram_socket).with('0.0.0.0', 0,
83
+ Frisky::SSDP::Searcher, "I'm a string", {})
84
+ subject.search(search_target)
85
+ end
86
+ end
87
+
88
+ context 'reactor is already running' do
89
+ it 'returns a Frisky::SSDP::Searcher' do
90
+ allow(EM).to receive(:reactor_running?).and_return true
91
+ expect(subject.search).to eq multicast_searcher
92
+ end
93
+ end
94
+
95
+ context 'reactor is not already running' do
96
+ context 'options hash includes do_broadcast_search' do
97
+ before do
98
+ allow(EM).to receive(:open_datagram_socket).
99
+ and_return(multicast_searcher, broadcast_searcher)
100
+ end
101
+
102
+ it 'returns an Array of responses' do
103
+ allow(EM).to receive(:add_shutdown_hook).and_yield
104
+ expect(subject.search(:all, do_broadcast_search: true)).to eq %w[one two three four]
105
+ end
106
+
107
+ it 'opens 2 UDP sockets on 0.0.0.0, port 0' do
108
+ allow(EM).to receive(:add_shutdown_hook)
109
+ expect(EM).to receive(:open_datagram_socket).with('0.0.0.0', 0, Frisky::SSDP::Searcher,
110
+ 'ssdp:all', {})
111
+ expect(EM).to receive(:open_datagram_socket).with('0.0.0.0', 0, Frisky::SSDP::BroadcastSearcher,
112
+ 'ssdp:all', 5, 4)
113
+ subject.search(:all, do_broadcast_search: true)
114
+ end
115
+ end
116
+
117
+ context 'options hash does not include do_broadcast_search' do
118
+ it 'returns an Array of responses' do
119
+ allow(EM).to receive(:add_shutdown_hook).and_yield
120
+ expect(subject.search).to eq %w[one two]
121
+ end
122
+
123
+ it 'opens a UDP socket on 0.0.0.0, port 0' do
124
+ allow(EM).to receive(:add_shutdown_hook)
125
+ expect(EM).to receive(:open_datagram_socket).with('0.0.0.0', 0, Frisky::SSDP::Searcher,
126
+ 'ssdp:all', {})
127
+ subject.search
128
+ end
129
+ end
130
+ end
131
+ end
132
+
133
+ describe '.notify' do
134
+ pending 'Implementation of UPnP Devices'
135
+ end
136
+
137
+ describe '.send_notification' do
138
+ pending 'Implementation of UPnP Devices'
139
+ end
140
+
141
+ =begin
142
+ context 'by default' do
143
+ it "searches for 'ssdp:all'" do
144
+ pending
145
+ end
146
+
147
+ it 'waits for 5 seconds for responses' do
148
+ before = Time.now
149
+
150
+ SSDP.search
151
+
152
+ after = Time.now
153
+ (after - before).should < 5.1
154
+ (after - before).should > 5.0
155
+ end
156
+ end
157
+
158
+ context "finds 'upnp:rootdevice's" do
159
+ it "by using the spec's string 'upnp:rootdevice'" do
160
+ SSDP.search('upnp:rootdevice').should == [
161
+ {
162
+ :cache_control=>'max-age=1200',
163
+ :date=>'Mon, 26 Sep 2011 06:40:19 GMT',
164
+ :location=>'http://192.168.10.3:5001/description/fetch',
165
+ :server=>'Linux-i386-2.6.38-10-generic-pae, UPnP/1.0, PMS/1.25.1',
166
+ :st=>'upnp:rootdevice',
167
+ :ext=>'',
168
+ :usn=>'uuid:3c202906-992d-3f0f-b94c-90e1902a136d::upnp:rootdevice',
169
+ :content_length=>'0'
170
+ }
171
+ ]
172
+ end
173
+
174
+ it 'by using :root' do
175
+ pending
176
+ end
177
+ end
178
+
179
+ it 'can wait for user-defined seconds for responses' do
180
+ before = Time.now
181
+
182
+ SSDP.search(:all, 1)
183
+
184
+ after = Time.now
185
+ (after - before).should < 1.1
186
+ (after - before).should > 1.0
187
+ end
188
+
189
+ it 'finds a device by its URN' do
190
+ pending
191
+ end
192
+
193
+ it 'finds a device by its UUID' do
194
+ pending
195
+ end
196
+
197
+ it 'finds a device by its UPnP device type' do
198
+ pending
199
+ end
200
+
201
+ it 'finds a device by its UPnP device type using a non-standard domain name' do
202
+ pending
203
+ end
204
+
205
+ it 'finds a service by its UPnP service type' do
206
+ pending
207
+ end
208
+
209
+ it 'find a service by its UPnP service type using a non-standard domain name' do
210
+ pending
211
+ end
212
+ =end
213
+ end
214
+
data/tasks/search.thor ADDED
@@ -0,0 +1,35 @@
1
+ require 'pp'
2
+ require File.expand_path(File.dirname(__FILE__)+ '/../lib/frisky/ssdp')
3
+ require File.expand_path(File.dirname(__FILE__)+ '/../lib/frisky/logger')
4
+
5
+ module Frisky
6
+ class Ssdp < Thor
7
+ #---------------------------------------------------------------------------
8
+ # search
9
+ #---------------------------------------------------------------------------
10
+ desc 'search TARGET', 'Searches for devices of type TARGET'
11
+ method_option :response_wait_time, default: 5
12
+ method_option :ttl, default: 4
13
+ method_option :do_broadcast_search, type: :boolean
14
+ method_option :log, type: :boolean
15
+ def search(target='upnp:rootdevice')
16
+ ::Frisky.logging_enabled = options[:log]
17
+ time_before = Time.now
18
+ results = ::Frisky::SSDP.search(target, options.dup)
19
+ .map { |r| r[:location] }
20
+ .sort
21
+ unique = results.uniq
22
+ time_after = Time.now
23
+
24
+ puts <<-RESULTS
25
+ size: #{results.size}
26
+ locations: #{results.join("\n ")}
27
+ unique size: #{unique.size}
28
+ unique locations: #{unique.join("\n ")}
29
+ search duration: #{time_after - time_before}
30
+ RESULTS
31
+
32
+ results
33
+ end
34
+ end
35
+ end
metadata ADDED
@@ -0,0 +1,181 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: frisky
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jon Frisby
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-09-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: eventmachine
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 1.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 1.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: em-http-request
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.0.2
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 1.0.2
41
+ - !ruby/object:Gem::Dependency
42
+ name: em-synchrony
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: nori
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 2.0.2
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 2.0.2
69
+ - !ruby/object:Gem::Dependency
70
+ name: log_switch
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 1.0.0
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 1.0.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: savon
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.0'
97
+ description: |-
98
+ frisky provides the tools you need to build an app that runs
99
+ in a UPnP environment.
100
+ email: jfrisby@mrjoy.com
101
+ executables: []
102
+ extensions: []
103
+ extra_rdoc_files:
104
+ - History.md
105
+ - README.md
106
+ - LICENSE.md
107
+ files:
108
+ - ".gemtest"
109
+ - ".gitignore"
110
+ - ".rspec"
111
+ - ".travis.yml"
112
+ - Gemfile
113
+ - History.md
114
+ - LICENSE.md
115
+ - README.md
116
+ - Rakefile
117
+ - features/device_discovery.feature
118
+ - features/step_definitions/.gitkeep
119
+ - features/support/env.rb
120
+ - features/support/world_extensions.rb
121
+ - frisky.gemspec
122
+ - lib/core_ext/hash_patch.rb
123
+ - lib/core_ext/socket_patch.rb
124
+ - lib/core_ext/to_upnp_s.rb
125
+ - lib/frisky.rb
126
+ - lib/frisky/logger.rb
127
+ - lib/frisky/ssdp.rb
128
+ - lib/frisky/ssdp/broadcast_searcher.rb
129
+ - lib/frisky/ssdp/error.rb
130
+ - lib/frisky/ssdp/listener.rb
131
+ - lib/frisky/ssdp/multicast_connection.rb
132
+ - lib/frisky/ssdp/network_constants.rb
133
+ - lib/frisky/ssdp/notifier.rb
134
+ - lib/frisky/ssdp/searcher.rb
135
+ - lib/frisky/version.rb
136
+ - spec/spec_helper.rb
137
+ - spec/support/search_responses.rb
138
+ - spec/unit/core_ext/to_upnp_s_spec.rb
139
+ - spec/unit/frisky/ssdp/listener_spec.rb
140
+ - spec/unit/frisky/ssdp/multicast_connection_spec.rb
141
+ - spec/unit/frisky/ssdp/notifier_spec.rb
142
+ - spec/unit/frisky/ssdp/searcher_spec.rb
143
+ - spec/unit/frisky/ssdp_spec.rb
144
+ - tasks/search.thor
145
+ homepage: http://github.com/MrJoy/frisky
146
+ licenses: []
147
+ metadata: {}
148
+ post_install_message:
149
+ rdoc_options: []
150
+ require_paths:
151
+ - lib
152
+ required_ruby_version: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - ">="
155
+ - !ruby/object:Gem::Version
156
+ version: 1.9.1
157
+ required_rubygems_version: !ruby/object:Gem::Requirement
158
+ requirements:
159
+ - - ">="
160
+ - !ruby/object:Gem::Version
161
+ version: '0'
162
+ requirements: []
163
+ rubyforge_project:
164
+ rubygems_version: 2.4.5.1
165
+ signing_key:
166
+ specification_version: 4
167
+ summary: Use me to build a UPnP app!
168
+ test_files:
169
+ - features/device_discovery.feature
170
+ - features/step_definitions/.gitkeep
171
+ - features/support/env.rb
172
+ - features/support/world_extensions.rb
173
+ - spec/spec_helper.rb
174
+ - spec/support/search_responses.rb
175
+ - spec/unit/core_ext/to_upnp_s_spec.rb
176
+ - spec/unit/frisky/ssdp/listener_spec.rb
177
+ - spec/unit/frisky/ssdp/multicast_connection_spec.rb
178
+ - spec/unit/frisky/ssdp/notifier_spec.rb
179
+ - spec/unit/frisky/ssdp/searcher_spec.rb
180
+ - spec/unit/frisky/ssdp_spec.rb
181
+ has_rdoc: