frisky 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2deaa661a30948e0d168faa316a7bb81f089ce59
4
+ data.tar.gz: 1cec1f6c3430a00632c11dfb9c1e2f1a1a49fc89
5
+ SHA512:
6
+ metadata.gz: 8715b77ee7c823c6ecad28f0a2110082e8952db901f91cdba1275a59f9fddf8852c5af0dddd35feb49185355a7ef41a0673a3f6d1685981091223b1df987398c
7
+ data.tar.gz: a39f0325d37d0c95ef0222baa92db352d5d2097baa4a7389467c7e678d7910ea2c699257a2b286de73eab003f0e2096294f1fc849fd0e41636651c8c52564681
data/.gemtest ADDED
File without changes
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+
5
+ .autotest
6
+ .DS_Store
7
+ .loadpath
8
+ .project
9
+ .yardoc/
10
+ .bundle/
11
+ .idea/
12
+
13
+ coverage/
14
+ doc/
15
+ pkg/
16
+ tmp/
17
+
18
+ */.DS_Store
19
+ atlassian-ide-plugin.xml
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - 2.1.0
6
+ - 2.2.3
data/Gemfile ADDED
@@ -0,0 +1,23 @@
1
+ # rubocop:disable Style/LeadingCommentSpace
2
+ #ruby-gemset=frisky
3
+ # rubocop:enable Style/LeadingCommentSpace
4
+ source 'http://rubygems.org'
5
+ gemspec
6
+ ruby "2.2.3"
7
+
8
+ #gem "httpi", '>=2.0.0.rc1'
9
+ #gem 'em-synchrony'
10
+
11
+ group :development do
12
+ gem 'coveralls', require: false
13
+ gem 'cucumber', '>=1.0.0', require: false
14
+ gem 'em-websocket', '>=0.3.6'
15
+ gem 'rake', require: false
16
+ gem 'log_buddy'
17
+ gem 'rspec', '>=3.0.0.beta', require: false
18
+ gem 'simplecov', '>=0.4.2', require: false
19
+ gem 'thin'
20
+ gem 'thor', '>=0.1.6', require: false
21
+ gem 'yard', '>=0.7.0', require: false
22
+ gem "pry", require: false
23
+ end
data/History.md ADDED
@@ -0,0 +1,3 @@
1
+ === 0.1.0.alpha.1
2
+
3
+ * I do stuffs!
data/LICENSE.md ADDED
@@ -0,0 +1,23 @@
1
+ (The MIT License)
2
+
3
+ Copyright (c) 2012-2014 Steve Loveless
4
+ Copyright (c) 2015 Jon Frisby
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining
7
+ a copy of this software and associated documentation files (the
8
+ 'Software'), to deal in the Software without restriction, including
9
+ without limitation the rights to use, copy, modify, merge, publish,
10
+ distribute, sublicense, and/or sell copies of the Software, and to
11
+ permit persons to whom the Software is furnished to do so, subject to
12
+ the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be
15
+ included in all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
18
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
20
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
21
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
22
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
23
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,185 @@
1
+ # Frisky
2
+
3
+ * [Homepage](http://github.com/MrJoy/frisky)
4
+ * [UPnP Device Architecture Documentation](http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.0.pdf)
5
+
6
+
7
+ [<img src="https://travis-ci.org/MrJoy/frisky.png?branch=master" alt="Build Status" />](https://travis-ci.org/MrJoy/frisky) [<img src="https://coveralls.io/repos/MrJoy/frisky/badge.png" alt="Coverage Status" />](https://coveralls.io/r/MrJoy/frisky)
8
+
9
+ ## Description
10
+
11
+ Ruby's UPnP RubyGem was outdated in Ruby 1.9 when support for Soap4r was
12
+ dropped. This gem intends to fill that void for Ruby >= 1.9 and allow for
13
+ SSDP search, discovery, advertisement, the ability to act as a UPnP control
14
+ point, as well as provide UPnP devices and services.
15
+
16
+ This uses [EventMachine](http://github.com/eventmachine/eventmachine), so if
17
+ you're not already, getting familiar with its concepts will be helpful here.
18
+
19
+ ### This Fork
20
+
21
+ This fork updates the [playful](http://github.com/turboladen/playful) gem,
22
+ which hasn't seen updates in a while.
23
+
24
+ Specifically:
25
+
26
+ 1. Brings a few dependencies up to date.
27
+ 2. Gets rid of unfinished/broken, overly-ambitious functionality.
28
+
29
+ In short, this fork aims to be a simple, minimal way to run SSDP queries and handle the
30
+ results.
31
+
32
+ ### Er, what's UPnP??
33
+
34
+ "Universal Plug and Play" is a mashup of network protocols that let network
35
+ devices identify themselves and discover and use each other's services.
36
+ Common implementations of UPnP devices are things like:
37
+
38
+ * [Media Servers and Clients](http://en.wikipedia.org/wiki/List_of_UPnP_AV_media_servers_and_clients) like...
39
+ * PS3
40
+ * Slingbox
41
+ * Xbox
42
+ * XBMC
43
+ * Plex
44
+ * VLC
45
+ * Twonky
46
+ * Mediatomb
47
+ * Home Automation
48
+ * Philips Hue
49
+
50
+
51
+ If you have a device that implements UPnP, you can most likely control it
52
+ programmatically with `frisky`. You can't today, but eventually you'll be
53
+ able to build your own devices & services with `frisky` that can be consumed
54
+ by other UPnP clients (ex. build a media server with frisky and listen on
55
+ your PS3...).
56
+
57
+ ## Features
58
+
59
+ ### Implemented
60
+
61
+ * SSDP search, discovery.
62
+
63
+
64
+ ### Coming
65
+
66
+ * UPnP Devices & Services (server)
67
+
68
+
69
+ ## Examples
70
+
71
+ Take a look at the `tasks` directory; I've created some working examples using
72
+ [Thor](https://github.com/wycats/thor). You can get a list of these tasks by
73
+ doing `thor -T`.
74
+
75
+ There's also a more involved, in-progress, working example at
76
+ http://github.com/turboladen/upnp_cp_on_sinatra that uses the Rack middleware
77
+ to build a Sinatra app that allows for controling devices in your network.
78
+
79
+ ### SSDP Searches
80
+
81
+ An SSDP search simply sends the M-SEARCH out to the multicast group and
82
+ listens for responses for a given (or default of 5 seconds) amount of time.
83
+ The return from this depends on if you're running it within an EventMachine
84
+ reactor or not. If not, it returns is an Array of responses as Hashes, where
85
+ keys are the header names, values are the header values. Take a look at the
86
+ SSDP.search docs for more on the options here.
87
+
88
+ ```ruby
89
+ require 'frisky/ssdp'
90
+
91
+ # Search for all devices (do an M-SEARCH with the ST header set to 'ssdp:all')
92
+ all_devices = Frisky::SSDP.search # this is default
93
+ all_devices = Frisky::SSDP.search 'ssdp:all' # or be explicit
94
+ all_devices = Frisky::SSDP.search :all # or use short-hand
95
+
96
+ # Search for root devices (do an M-SEARCH with ST header set to 'upnp:rootdevices')
97
+ root_devices = Frisky::SSDP.search 'upnp:rootdevices'
98
+ root_devices = Frisky::SSDP.search :root # or use short-hand
99
+
100
+ # Search for a device with a specific UUID
101
+ my_device = Frisky::SSDP.search 'uuid:3c202906-992d-3f0f-b94c-90e1902a136d'
102
+
103
+ # Search for devices of a specific type
104
+ my_media_server = Frisky::SSDP.search 'urn:schemas-upnp-org:device:MediaServer:1'
105
+
106
+ # All of these searches will return something that looks like
107
+ # => [
108
+ # {
109
+ # :control => "max-age=1200",
110
+ # :date => "Sun, 23 Sep 2012 20:31:48 GMT",
111
+ # :location => "http://192.168.10.3:5001/description/fetch",
112
+ # :server => "Linux-i386-2.6.38-15-generic-pae, UPnP/1.0, PMS/1.50.0",
113
+ # :st => "upnp:rootdevice",
114
+ # :ext => "",
115
+ # :usn => "uuid:3c202906-992d-3f0f-b94c-90e1902a136d::upnp:rootdevice",
116
+ # :length => "0"
117
+ # }
118
+ # ]
119
+ ```
120
+
121
+ If you do the search inside of an EventMachine reactor, as the
122
+ Frisky::SSDP::Searcher receives and parses responses, it adds them to the
123
+ accessor #discovery_responses, which is an EventMachine::Channel. This lets
124
+ you subscribe to the resposnes and do what you want with them as you receive them.
125
+
126
+ ```ruby
127
+ require 'frisky/ssdp'
128
+
129
+ EM.run do
130
+ searcher = Frisky::SSDP.search 'uuid:3c202906-992d-3f0f-b94c-90e1902a136d'
131
+
132
+ # Create a deferrable object that can be notified when the device we want
133
+ # has been found and created.
134
+ device_controller = EventMachine::DefaultDeferrable.new
135
+
136
+ # This callback will get called when the device_creator callback is called
137
+ # (which is called after the device has been created).
138
+ device_controller.callback do |device|
139
+ p device.service_list.first.service_type # "urn:schemas-upnp-org:service:ContentDirectory:1"
140
+
141
+ # SOAP actions are converted to Ruby methods--show those
142
+ p device.service_list.first.singleton_methods # [:GetSystemUpdateID, :Search, :GetSearchCapabilities, :GetSortCapabilities, :Browse]
143
+
144
+ # Call a SOAP method defined in the service. The response is extracted from the
145
+ # XML SOAP response and the value is converted from the UPnP dataType to
146
+ # the related Ruby type. Reponses are always contained in a Hash, so as
147
+ # to maintain the relation defined in the service.
148
+ p device.service_list.first.GetSystemUpdateID # { :Id => 1 }
149
+ end
150
+
151
+ # Note that you don't have to check for items in the Channel or for when the
152
+ # Channel is empty: EventMachine will pop objects off the Channel as soon as
153
+ # they're put there and stop when there are none left.
154
+ searcher.discovery_responses.pop do |notification|
155
+ # Do stuff here.
156
+ end
157
+ end
158
+ ```
159
+
160
+ ## Requirements
161
+
162
+ * Rubies (tested)
163
+ * 1.9.3
164
+ * 2.0.0
165
+ * 2.1.0
166
+ * Gems
167
+ * eventmachine
168
+ * em-http-request
169
+ * em-synchrony
170
+ * nori
171
+ * log_switch
172
+ * savon
173
+
174
+
175
+
176
+ ## Install
177
+
178
+ $ gem install frisky
179
+
180
+ ## Copyright
181
+
182
+ Copyright (c) 2015 Jon Frisby
183
+ Copyright (c) 2012-2014 Steve Loveless
184
+
185
+ See LICENSE.md for details.
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'cucumber/rake/task'
4
+ require 'yard'
5
+
6
+
7
+ YARD::Rake::YardocTask.new
8
+ Cucumber::Rake::Task.new(:features)
9
+ RSpec::Core::RakeTask.new
10
+
11
+ # Alias for rubygems-test
12
+ desc "Run all test suites."
13
+ task test: [:spec, :features]
14
+
15
+ task default: :test
16
+
@@ -0,0 +1,9 @@
1
+ Feature: Device discovery
2
+ As a device controller, I want to be able to discover devices
3
+ so that I can use the services those devices provide
4
+
5
+ Scenario: A single root device
6
+ Given there's at least 1 root device in my network
7
+ When I come online
8
+ Then I should discover at least 1 root device
9
+ And the location of that device should match my fake device's location
File without changes
@@ -0,0 +1,21 @@
1
+ require 'socket'
2
+ require 'log_buddy'
3
+ require 'cucumber/rspec/doubles'
4
+ require_relative "../../lib/frisky/logger"
5
+
6
+ def local_ip_and_port
7
+ orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true
8
+
9
+ UDPSocket.open do |s|
10
+ s.connect '64.233.187.99', 1
11
+ s.addr.last
12
+ [s.addr.last, s.addr[1]]
13
+ end
14
+ ensure
15
+ Socket.do_not_reverse_lookup = orig
16
+ end
17
+
18
+ ENV['RUBY_UPNP_ENV'] = 'testing'
19
+
20
+ Thread.abort_on_exception = true
21
+ Frisky.logging_enabled = false
@@ -0,0 +1,7 @@
1
+ module HelperStuff
2
+ def local_ip
3
+ @local_ip ||= local_ip_and_port.first
4
+ end
5
+ end
6
+
7
+ World(HelperStuff)
data/frisky.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path('../lib', __FILE__)
3
+ require 'frisky/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'frisky'
7
+ s.version = Frisky::VERSION
8
+ s.author = 'Jon Frisby'
9
+ s.email = 'jfrisby@mrjoy.com'
10
+ s.homepage = 'http://github.com/MrJoy/frisky'
11
+ s.summary = 'Use me to build a UPnP app!'
12
+ s.description = %q{frisky 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.md README.md LICENSE.md)
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', '~>1.0.0'
27
+ s.add_dependency 'savon', '~>2.0'
28
+ end
@@ -0,0 +1,5 @@
1
+ class Hash
2
+ def symbolize_keys!
3
+ self.inject({}) { |result, (k, v)| result[k.to_sym] = v; result }
4
+ end
5
+ end
@@ -0,0 +1,16 @@
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
@@ -0,0 +1,65 @@
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