hive-runner-tv 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +53 -0
- data/lib/hive/controller/tv.rb +48 -0
- data/lib/hive/device/tv.rb +13 -0
- data/lib/hive/diagnostic/tv/dead.rb +44 -0
- data/lib/hive/diagnostic/tv/uptime.rb +31 -0
- data/lib/hive/messages/tv_job.rb +20 -0
- data/lib/hive/worker/tv.rb +226 -0
- metadata +92 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b6afb887754f6f8863ac063c54724322aeb1ea80
|
4
|
+
data.tar.gz: a3d420f7922168d075b88c4a6b6e34e2055fb735
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2a2fbaaf867d636638972f7c99c21921a8ec97b1125fea6455bdd9af9ccf8973ca733ebe4c4e936bb9a006367c31509f31b93c22d67eab5c8d117adca6480862
|
7
|
+
data.tar.gz: 3a9526b391be926e1a8d3aa5d4bf7053732ee498c68bdfb1126363ae8eed2a790a7a8a8b1f4374c815a60bb877b45aabec9e78607a4600625bb92b5953432cd5
|
data/README.md
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# hive-runner-tv
|
2
|
+
TV module for Hive Runner
|
3
|
+
|
4
|
+
## Configuration file
|
5
|
+
|
6
|
+
```
|
7
|
+
controllers:
|
8
|
+
tv:
|
9
|
+
ir_blaster_clients:
|
10
|
+
<devicedb id>:
|
11
|
+
type: <Blaster type, eg rat_blaster>
|
12
|
+
mac: <IR blaster mac address, eg 00-11-22-33-44-55>
|
13
|
+
dataset: <IR blaster dataset name>
|
14
|
+
output: <IP blaster output id>
|
15
|
+
sequences:
|
16
|
+
launch_titantv:
|
17
|
+
- signal:Power
|
18
|
+
- sleep:2
|
19
|
+
- signal:Power
|
20
|
+
|
21
|
+
network:
|
22
|
+
remote_talkshow_address: <talkshow url, eg talkshow.remote>
|
23
|
+
remote_talkshow_port_offset: <minimum port number>
|
24
|
+
tv:
|
25
|
+
titantv_url: <eg http://titantv.url/titantv>
|
26
|
+
titantv_name: <Application name reported by Titan TV>
|
27
|
+
ir_blaster_host: <ip address of IR blaster hub, eg, 10.20.30.40>
|
28
|
+
ir_blaster_port: <port number of IR blaster hub>
|
29
|
+
|
30
|
+
diagnostics:
|
31
|
+
tv:
|
32
|
+
uptime:
|
33
|
+
reboot_time: <seconds between reboots>
|
34
|
+
dead:
|
35
|
+
```
|
36
|
+
|
37
|
+
Note, the `ir\_blaster\_clients` section under `controllers` may ultimately
|
38
|
+
move to the information provided by the Device Database.
|
39
|
+
|
40
|
+
## IR Blaster
|
41
|
+
|
42
|
+
To use an IR blaster with `hive-runner` add the line
|
43
|
+
|
44
|
+
gem 'device_api-tv', git: 'git@github.com:bbc/device_api-tv'
|
45
|
+
|
46
|
+
to the Hive's Gemfile and add the configuration as shown above.
|
47
|
+
|
48
|
+
## License
|
49
|
+
|
50
|
+
Hive Runner TV is available to everyone under the terms of the MIT open source licence.
|
51
|
+
Take a look at the LICENSE file in the code.
|
52
|
+
|
53
|
+
Copyright (c) 2016 BBC
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'hive/controller'
|
2
|
+
require 'hive/worker/tv'
|
3
|
+
|
4
|
+
module Hive
|
5
|
+
class Controller
|
6
|
+
# The TV controller
|
7
|
+
class Tv < Controller
|
8
|
+
@@exclusion = []
|
9
|
+
|
10
|
+
def self.add_exclusion ex
|
11
|
+
@@exclusion << ex
|
12
|
+
end
|
13
|
+
|
14
|
+
def detect
|
15
|
+
Hive.logger.debug("Checking Hive Mind")
|
16
|
+
device_list = Hive.hive_mind.device_details['connected_devices']
|
17
|
+
Hive.logger.debug("Device list: #{device_list}")
|
18
|
+
devices = []
|
19
|
+
if device_list.is_a? Array
|
20
|
+
device_list.select { |d| valid? d }.collect do |device|
|
21
|
+
Hive.logger.debug("Found TV: #{device.inspect}")
|
22
|
+
devices << self.create_device(device)
|
23
|
+
end
|
24
|
+
else
|
25
|
+
raise Hive::Controller::DeviceDetectionFailed
|
26
|
+
end
|
27
|
+
Hive.logger.debug("Devices: #{devices}")
|
28
|
+
devices
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def valid? device
|
33
|
+
if device['device_type'] == 'Tv'
|
34
|
+
@@exclusion.each do |ex|
|
35
|
+
match = true
|
36
|
+
ex.each_pair do |key, value|
|
37
|
+
match = match && device[key.to_s] != value
|
38
|
+
end
|
39
|
+
return false if ! match
|
40
|
+
end
|
41
|
+
return true
|
42
|
+
else
|
43
|
+
false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'hive/diagnostic'
|
2
|
+
|
3
|
+
module Hive
|
4
|
+
class Diagnostic
|
5
|
+
class Tv
|
6
|
+
class Dead < Diagnostic
|
7
|
+
def diagnose
|
8
|
+
status = @device_api.status
|
9
|
+
case status
|
10
|
+
when 'idle'
|
11
|
+
self.pass('Device is idle')
|
12
|
+
else
|
13
|
+
self.fail("Device is #{status}")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def repair(result)
|
18
|
+
tries = [
|
19
|
+
[ :launch_titantv ],
|
20
|
+
[ :power_cycle, :launch_titantv ],
|
21
|
+
[ :power_on, :launch_titantv ]
|
22
|
+
]
|
23
|
+
|
24
|
+
tries.each do |commands|
|
25
|
+
catch :commands_failed do
|
26
|
+
commands.each do |command|
|
27
|
+
throw :commands_failed if ! @device_api.run_sequence(command)
|
28
|
+
end
|
29
|
+
timeout = Time.now + 600
|
30
|
+
while Time.now < timeout
|
31
|
+
if @device_api.status == 'idle'
|
32
|
+
return self.pass('Titan TV launched')
|
33
|
+
end
|
34
|
+
sleep 5
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
self.fail("Failed to recover device")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'hive/diagnostic'
|
2
|
+
|
3
|
+
module Hive
|
4
|
+
class Diagnostic
|
5
|
+
class Tv
|
6
|
+
class Uptime < Diagnostic
|
7
|
+
def initialize(config, options)
|
8
|
+
@next_reboot_time = Time.now + config[:reboot_timeout] if config.has_key?(:reboot_timeout)
|
9
|
+
super(config, options)
|
10
|
+
end
|
11
|
+
|
12
|
+
def diagnose
|
13
|
+
if config.has_key?(:reboot_timeout)
|
14
|
+
if Time.now < @next_reboot_time
|
15
|
+
self.pass("Time to next reboot: #{@next_reboot_time - Time.now}s")
|
16
|
+
else
|
17
|
+
self.fail("Reboot required")
|
18
|
+
end
|
19
|
+
else
|
20
|
+
self.pass("Not configured to reboot")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def repair(result)
|
25
|
+
@next_reboot_time += config[:reboot_timeout]
|
26
|
+
@device_api.power_cycle ? self.pass("Successful reboot") : self.fail("Reboot failed")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'hive/messages'
|
4
|
+
|
5
|
+
module Hive
|
6
|
+
module Messages
|
7
|
+
class TvJob < Hive::Messages::Job
|
8
|
+
# TODO Fix this validation
|
9
|
+
# validates :application_url, :application_url_parameters, presence: true
|
10
|
+
|
11
|
+
def application_url
|
12
|
+
self.target.symbolize_keys[:application_url]
|
13
|
+
end
|
14
|
+
|
15
|
+
def application_url_parameters
|
16
|
+
self.target.symbolize_keys[:application_url_parameters]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
require 'hive/worker'
|
2
|
+
require 'hive/messages/tv_job'
|
3
|
+
require 'mind_meld/tv'
|
4
|
+
require 'talkshow'
|
5
|
+
|
6
|
+
module Hive
|
7
|
+
class Worker
|
8
|
+
# The TV worker
|
9
|
+
class Tv < Worker
|
10
|
+
class FailedRedirect < StandardError
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(config)
|
14
|
+
@brand = config['brand'].downcase.gsub(/\s/, '_')
|
15
|
+
@model = config['model'].downcase.gsub(/\s/, '_')
|
16
|
+
|
17
|
+
if config['ir_blaster_clients'] and config['ir_blaster_clients'].has_key?(config['id'])
|
18
|
+
require 'device_api/tv'
|
19
|
+
DeviceAPI::RatBlaster.configure do |rb_config|
|
20
|
+
rb_config.host = Hive.config.network.tv.ir_blaster_host if Hive.config.network.tv.ir_blaster_host?
|
21
|
+
rb_config.port = Hive.config.network.tv.ir_blaster_port if Hive.config.network.tv.ir_blaster_port?
|
22
|
+
end
|
23
|
+
|
24
|
+
config.merge!({"device_api" => DeviceAPI::TV::Device.new(
|
25
|
+
id: config['id'],
|
26
|
+
ir: {
|
27
|
+
type: config['ir_blaster_clients'][config['id']].type,
|
28
|
+
mac: config['ir_blaster_clients'][config['id']].mac,
|
29
|
+
dataset: config['ir_blaster_clients'][config['id']].dataset,
|
30
|
+
output: config['ir_blaster_clients'][config['id']].output
|
31
|
+
}
|
32
|
+
)})
|
33
|
+
config['ir_blaster_clients'][config['id']].sequences.each do |name, pattern|
|
34
|
+
config['device_api'].set_sequence(name.to_sym, pattern)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
super(config)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Prepare the TV
|
41
|
+
def pre_script(job, job_paths, script)
|
42
|
+
url = job.application_url
|
43
|
+
@log.info("Application url: #{url}")
|
44
|
+
# TODO Set device as busy in Hive mind
|
45
|
+
|
46
|
+
params = ""
|
47
|
+
|
48
|
+
# 'cross_network_restriction' means that the device requires Talkshow
|
49
|
+
# on the same network as the application.
|
50
|
+
# For the moment, assume networks are '10.10.*.*' and'*.bbc.co.uk'
|
51
|
+
# TODO Make this more general
|
52
|
+
if @options['features'] && @options['features'].include?('cross_network_restriction') and /bbc.co.uk/.match url
|
53
|
+
ts_address = Hive.config.network.remote_talkshow_address
|
54
|
+
ts_port = @ts_port = Hive.config.network.remote_talkshow_port_offset + @options['id']
|
55
|
+
@log.info("Using remote talkshow on port #{ts_port}")
|
56
|
+
script.set_env 'TALKSHOW_REMOTE_URL', "http://#{ts_address}:#{ts_port}"
|
57
|
+
# Not actually required but talkshow fails without it set
|
58
|
+
script.set_env 'TALKSHOW_PORT', ts_port
|
59
|
+
else
|
60
|
+
ts_port = @ts_port = self.allocate_port
|
61
|
+
@log.info("Using talkshow on port #{ts_port}")
|
62
|
+
# TODO Move this more centrally
|
63
|
+
ip = Socket.ip_address_list.detect { |intf| intf.ipv4_private? }
|
64
|
+
ts_address = ip.ip_address
|
65
|
+
script.set_env 'TALKSHOW_PORT', ts_port
|
66
|
+
end
|
67
|
+
@log.info("Talkshow server address is: #{ts_address}")
|
68
|
+
|
69
|
+
if job.application_url_parameters.present?
|
70
|
+
params = "?#{job.application_url_parameters}&talkshowurl=#{ts_address}:#{ts_port}"
|
71
|
+
else
|
72
|
+
params = "?talkshowurl=#{ts_address}:#{ts_port}"
|
73
|
+
end
|
74
|
+
url += params
|
75
|
+
|
76
|
+
self.redirect(
|
77
|
+
url: url,
|
78
|
+
old_app: Hive.config.network.tv.titantv_name,
|
79
|
+
skip_first_load: true
|
80
|
+
)
|
81
|
+
load_hive_mind ts_port, url
|
82
|
+
|
83
|
+
@log.info("Starting TV Application monitor")
|
84
|
+
@monitor = Thread.new do
|
85
|
+
loop do
|
86
|
+
poll_response = Hive.hive_mind.poll(@device_id)
|
87
|
+
# if poll_response.is_a? Array and poll_response.length > 0
|
88
|
+
# @log.debug("[TV app monitor] Polled TV. Application = #{poll_response.first['application']}")
|
89
|
+
# # if @hive_mind.device_details(refresh: true)['application'] == Hive.config.network.tv.titantv_name
|
90
|
+
# if poll_response.first['application'] == Hive.config.network.tv.titantv_name
|
91
|
+
# # TV has returned to the holding app
|
92
|
+
# # Put back in the app under test
|
93
|
+
# self.redirect(
|
94
|
+
# url: url,
|
95
|
+
# old_app: Hive.config.network.tv.titantv_name,
|
96
|
+
# log_prefix: '[TV app monitor] '
|
97
|
+
# )
|
98
|
+
# end
|
99
|
+
# else
|
100
|
+
# @log.warn("[TV app monitor] Failed to poll TV")
|
101
|
+
# end
|
102
|
+
sleep 20
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
return nil
|
107
|
+
end
|
108
|
+
|
109
|
+
def job_message_klass
|
110
|
+
Hive::Messages::TvJob
|
111
|
+
end
|
112
|
+
|
113
|
+
def mind_meld_klass
|
114
|
+
MindMeld::Tv
|
115
|
+
end
|
116
|
+
|
117
|
+
def post_script(job, job_paths, script)
|
118
|
+
self.release_port(@ts_port) if @ts_port
|
119
|
+
|
120
|
+
signal_safe_post_script(job, job_paths, script)
|
121
|
+
end
|
122
|
+
|
123
|
+
def signal_safe_post_script(job, job_paths, script)
|
124
|
+
@log.info('Terminating TV Application monitor')
|
125
|
+
@monitor.exit if @monitor
|
126
|
+
|
127
|
+
self.redirect(url: Hive.config.network.tv.titantv_url, new_app: Hive.config.network.tv.titantv_name, skip_last_load: true)
|
128
|
+
# TODO Set device as idle in Hive Mind
|
129
|
+
end
|
130
|
+
|
131
|
+
def device_status
|
132
|
+
@hive_mind.device_details['status']
|
133
|
+
end
|
134
|
+
|
135
|
+
def set_device_status(status)
|
136
|
+
# TODO Set status from Hive Mind
|
137
|
+
# At the moment the device state cannot be changed
|
138
|
+
device_status
|
139
|
+
end
|
140
|
+
|
141
|
+
def autogenerated_queues
|
142
|
+
[ "#{@brand}-#{@model}" ]
|
143
|
+
end
|
144
|
+
|
145
|
+
def redirect(opts)
|
146
|
+
raise ArgumentError if ! ( opts.has_key?(:url) && ( opts.has_key?(:old_app) || opts.has_key?(:new_app) ) )
|
147
|
+
load_hive_mind(@ts_port, opts[:old_app] || "Trying to redirect ...") if ! opts[:skip_first_load]
|
148
|
+
opts[:log_prefix] ||= ''
|
149
|
+
@log.info("#{opts[:log_prefix]}Redirecting to #{opts[:url]}")
|
150
|
+
@hive_mind.create_action(action_type: 'redirect', body: opts[:url])
|
151
|
+
sleep 5
|
152
|
+
load_hive_mind(@ts_port, opts[:url]) if ! opts[:skip_last_load]
|
153
|
+
|
154
|
+
max_wait_count = 30
|
155
|
+
wait_count = 0
|
156
|
+
max_retry_count = 15
|
157
|
+
retry_count = 0
|
158
|
+
|
159
|
+
app_name = @hive_mind.device_details(refresh: true)['application']
|
160
|
+
@log.debug("#{opts[:log_prefix]}Current app: #{app_name}")
|
161
|
+
while (opts.has_key?(:new_app) && app_name != opts[:new_app]) || (opts.has_key?(:old_app) && app_name == opts[:old_app])
|
162
|
+
if wait_count >= max_wait_count
|
163
|
+
if retry_count >= max_retry_count
|
164
|
+
raise FailedRedirect
|
165
|
+
else
|
166
|
+
retry_count += 1
|
167
|
+
wait_count = 0
|
168
|
+
@log.info("#{opts[:log_prefix]}Redirecting to #{opts[:url]} [#{retry_count}]")
|
169
|
+
@hive_mind.create_action(action_type: 'redirect', body: opts[:url])
|
170
|
+
sleep 5
|
171
|
+
end
|
172
|
+
else
|
173
|
+
wait_count += 1
|
174
|
+
@log.info("#{opts[:log_prefix]} . [#{wait_count}]")
|
175
|
+
sleep 5
|
176
|
+
load_hive_mind(@ts_port, opts[:url]) if ! opts[:skip_last_load]
|
177
|
+
end
|
178
|
+
app_name = @hive_mind.device_details(refresh: true)['application']
|
179
|
+
@log.debug("#{opts[:log_prefix]}Current app: #{app_name}")
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# Between tests the TV must be in the holding app
|
184
|
+
def diagnostics
|
185
|
+
app_name = @hive_mind.device_details(refresh: true)['application']
|
186
|
+
raise DeviceNotReady.new("Current application: '#{app_name}'") if app_name != Hive.config.network.tv.titantv_name
|
187
|
+
super
|
188
|
+
end
|
189
|
+
|
190
|
+
def load_hive_mind ts_port, app_name
|
191
|
+
ts = Talkshow.new
|
192
|
+
@log.info("Port: #{ts_port}")
|
193
|
+
@log.info("App: #{app_name}")
|
194
|
+
@log.info("Logfile: #{@file_system.results_path}/talkshowserver.log")
|
195
|
+
@log.info("titantv_url: #{Hive.config.network.tv.titantv_url}")
|
196
|
+
ts.start_server(port: ts_port, logfile: "#{@file_system.results_path}/talkshowserver.log")
|
197
|
+
5.times do
|
198
|
+
begin
|
199
|
+
ts.execute <<JS
|
200
|
+
(function(){
|
201
|
+
var load_script = document.createElement('script');
|
202
|
+
load_script.type = 'text/javascript';
|
203
|
+
load_script.charset = 'utf-8';
|
204
|
+
load_script.src = '#{Hive.config.network.tv.titantv_url}/script/hive_mind_com.js';
|
205
|
+
document.getElementsByTagName('head')[0].appendChild(load_script);
|
206
|
+
// Give it 10 seconds to load
|
207
|
+
// TODO Do this with a retry
|
208
|
+
setTimeout(function() {
|
209
|
+
hive_mind_com.init('#{app_name}', '#{Hive.config.network.tv.titantv_url}');
|
210
|
+
hive_mind_com.start();
|
211
|
+
}, 10000);
|
212
|
+
return true;
|
213
|
+
})()
|
214
|
+
JS
|
215
|
+
break
|
216
|
+
rescue Talkshow::Timeout
|
217
|
+
@log.info("Talkshow timeout")
|
218
|
+
sleep 5
|
219
|
+
end
|
220
|
+
end
|
221
|
+
ts.stop_server
|
222
|
+
end
|
223
|
+
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
metadata
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hive-runner-tv
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Joe Haig
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-09-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: hive-runner
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: talkshow
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.4.1
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.4.1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: res
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.2'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.2'
|
55
|
+
description: The TV controller module for Hive Runner
|
56
|
+
email: joe.haig@bbc.co.uk
|
57
|
+
executables: []
|
58
|
+
extensions: []
|
59
|
+
extra_rdoc_files: []
|
60
|
+
files:
|
61
|
+
- README.md
|
62
|
+
- lib/hive/controller/tv.rb
|
63
|
+
- lib/hive/device/tv.rb
|
64
|
+
- lib/hive/diagnostic/tv/dead.rb
|
65
|
+
- lib/hive/diagnostic/tv/uptime.rb
|
66
|
+
- lib/hive/messages/tv_job.rb
|
67
|
+
- lib/hive/worker/tv.rb
|
68
|
+
homepage: https://github.com/bbc-test/hive-runner-tv
|
69
|
+
licenses:
|
70
|
+
- MIT
|
71
|
+
metadata: {}
|
72
|
+
post_install_message:
|
73
|
+
rdoc_options: []
|
74
|
+
require_paths:
|
75
|
+
- lib
|
76
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
requirements: []
|
87
|
+
rubyforge_project:
|
88
|
+
rubygems_version: 2.5.0
|
89
|
+
signing_key:
|
90
|
+
specification_version: 4
|
91
|
+
summary: Hive Runner TV
|
92
|
+
test_files: []
|