bandshell 0.9 → 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.
- checksums.yaml +4 -4
- data/lib/bandshell/application/app.rb +52 -143
- data/lib/bandshell/application/public/favicon.ico +0 -0
- data/lib/bandshell/application/public/javascripts/authenticate.js +39 -6
- data/lib/bandshell/application/public/javascripts/jquery-1.7.2.js +9404 -0
- data/lib/bandshell/application/views/authenticate.erb +41 -8
- data/lib/bandshell/application/views/main.erb +2 -2
- data/lib/bandshell/application/views/player_status.erb +16 -2
- data/lib/bandshell/hardware_api.rb +54 -9
- data/lib/bandshell/player_info.rb +112 -0
- data/lib/bandshell/screen_control.rb +159 -0
- metadata +6 -2
@@ -3,13 +3,46 @@
|
|
3
3
|
<div id="no-js">
|
4
4
|
It looks like Javascript is disabled. The Concerto Display will not work.
|
5
5
|
</div>
|
6
|
-
<% unless @display_temp_token.nil? %>
|
7
|
-
<div id="instructions">
|
8
|
-
Please enter the following token when setting up the screen in the
|
9
|
-
Concerto Panel:
|
10
|
-
<%= @display_temp_token %>
|
11
|
-
</div>
|
12
|
-
<% end %>
|
13
6
|
<div id="status">
|
14
7
|
Waiting for first update...
|
15
|
-
</div>
|
8
|
+
</div>
|
9
|
+
<div id="error" style="display:none">
|
10
|
+
<div class="alert alert-block">
|
11
|
+
<h2><span id="error_text">Error text here.</span></h2>
|
12
|
+
<p><span id="error_res_text">Error resolution info here.</span></p>
|
13
|
+
</div>
|
14
|
+
</div>
|
15
|
+
<div id="instructions" style="display:none">
|
16
|
+
<div class="alert alert-block">
|
17
|
+
<p class="center">
|
18
|
+
To configure this screen, you will need the following code:
|
19
|
+
</p>
|
20
|
+
<p class="center" style="font-size: 4em; font-family: Courier New, monospace; line-height: 1em;">
|
21
|
+
<%# The following span will be filled in by javascript. %>
|
22
|
+
<strong><span id="screen_temp_token">xxxxxx</span></strong>
|
23
|
+
</p>
|
24
|
+
</div>
|
25
|
+
<div style="margin: 20px auto; max-width: 850px;">
|
26
|
+
<br />
|
27
|
+
<h2><b>Step One.</b><br />
|
28
|
+
On your PC or mobile device, log into the
|
29
|
+
Concerto Panel and visit the Screens page.
|
30
|
+
</h2>
|
31
|
+
<br />
|
32
|
+
<h2><b>Step Two.</b><br />If this is a completely new screen, click
|
33
|
+
"New Screen" and fill out the relevant details.</h2>
|
34
|
+
The screen should not be public. Use the code
|
35
|
+
given above as the authorization code. If this
|
36
|
+
machine is being reconfigured or is replacing an
|
37
|
+
existing screen, simply click on that screen, click
|
38
|
+
edit, ensure that the screen is not public, and enter
|
39
|
+
the authorization code.
|
40
|
+
<br />
|
41
|
+
<h2><b>Step Three.</b><br />Finally, take a look back at this screen. After a moment,
|
42
|
+
your template should appear.</h2>
|
43
|
+
<p>You can add some content to the screen by editing the
|
44
|
+
subscriptions in each field in the Concerto Panel.</p>
|
45
|
+
<br />
|
46
|
+
<h1 class="center">Happy Advertising!</h1>
|
47
|
+
</div>
|
48
|
+
</div>
|
@@ -6,7 +6,7 @@
|
|
6
6
|
<meta name="application-name" content="Concerto Player Network Configuration"/>
|
7
7
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
8
8
|
<link rel="stylesheet" type="text/css" href="/stylesheets/concerto-styles.css">
|
9
|
-
<script src="
|
9
|
+
<script src="javascripts/jquery-1.7.2.js"></script>
|
10
10
|
</head>
|
11
11
|
<body>
|
12
12
|
<%#= render :partial => "elements/topmenu_contents" %>
|
@@ -52,4 +52,4 @@
|
|
52
52
|
</div>
|
53
53
|
|
54
54
|
</body>
|
55
|
-
</html>
|
55
|
+
</html>
|
@@ -15,6 +15,20 @@
|
|
15
15
|
<%= JSON.pretty_generate(@on_off_rules) %>
|
16
16
|
</pre>
|
17
17
|
<p>
|
18
|
-
Screen
|
19
|
-
<%= screen_scheduled_on? ? "On" : "Off" %>
|
18
|
+
Screen scheduled state:
|
19
|
+
<%= player_info.screen_scheduled_on? ? "On" : "Off" %>
|
20
|
+
</p>
|
21
|
+
<p>
|
22
|
+
Screen control availability:
|
23
|
+
<% avail, msg = Bandshell::ScreenControl.control_availability %>
|
24
|
+
<% if avail %>
|
25
|
+
Available
|
26
|
+
<% else %>
|
27
|
+
Not available:<br/>
|
28
|
+
<%= msg %>
|
29
|
+
<% end %>
|
30
|
+
</p>
|
31
|
+
<p>
|
32
|
+
Screen actual state:
|
33
|
+
<%= Bandshell::ScreenControl.screen_is_on? ? "On" : "Off" %>
|
20
34
|
</p>
|
@@ -14,7 +14,9 @@ module Bandshell
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def concerto_url
|
17
|
-
|
17
|
+
# Trailing slash required for proper URI Join behavior.
|
18
|
+
# Double slashes not harmful.
|
19
|
+
Bandshell::ConfigStore.read_config('concerto_url', '')+"/"
|
18
20
|
end
|
19
21
|
|
20
22
|
def frontend_uri
|
@@ -35,13 +37,20 @@ module Bandshell
|
|
35
37
|
|
36
38
|
attr_reader :screen_id, :screen_url
|
37
39
|
|
40
|
+
# Can return:
|
41
|
+
# :stat_badauth
|
42
|
+
# :stat_err
|
43
|
+
# :stat_serverr on connection or sever failure
|
44
|
+
# :stat_badauth on an invalid permanent token
|
45
|
+
# :stat_success when screen data retrieved.
|
38
46
|
def attempt_to_get_screen_data!
|
39
47
|
unless have_temp_token? or have_auth_token?
|
40
48
|
request_temp_token!
|
41
49
|
end
|
42
50
|
|
43
51
|
unless have_auth_token?
|
44
|
-
check_temp_token!
|
52
|
+
tt_status = check_temp_token!
|
53
|
+
return tt_status unless tt_status == :stat_success
|
45
54
|
end
|
46
55
|
|
47
56
|
if have_auth_token?
|
@@ -50,6 +59,8 @@ module Bandshell
|
|
50
59
|
ConfigStore.write_config('auth_token','')
|
51
60
|
request_temp_token!
|
52
61
|
end
|
62
|
+
elsif have_temp_token?
|
63
|
+
status = :stat_temponly
|
53
64
|
else
|
54
65
|
status = :stat_err
|
55
66
|
end
|
@@ -66,6 +77,11 @@ module Bandshell
|
|
66
77
|
|
67
78
|
# Get array of data about the screen from the server
|
68
79
|
# This can only succeed once we have obtained a valid auth token.
|
80
|
+
# Returns:
|
81
|
+
# :stat_serverr on connection or sever failure
|
82
|
+
# :stat_badauth on an invalid permanent token
|
83
|
+
# :stat_success when screen data retrieved.
|
84
|
+
# TODO: save screen data in configs???
|
69
85
|
def fetch_screen_data
|
70
86
|
return nil if auth_token.empty?
|
71
87
|
|
@@ -104,15 +120,25 @@ module Bandshell
|
|
104
120
|
res = Net::HTTP.start(uri.hostname, uri.port) { |http|
|
105
121
|
http.request(req)
|
106
122
|
}
|
107
|
-
rescue
|
123
|
+
rescue StandardError => ex
|
124
|
+
puts "get_with_auth: Failed to access concerto server:\n"+
|
125
|
+
" "+ex.message.chomp
|
108
126
|
res = nil
|
109
127
|
end
|
110
128
|
res
|
111
129
|
end
|
112
130
|
|
113
131
|
def request_temp_token!
|
114
|
-
|
132
|
+
begin
|
133
|
+
response = Net::HTTP.get_response(frontend_api_uri)
|
134
|
+
rescue StandardError => ex
|
135
|
+
puts "request_temp_token: Failed to access concerto server:\n"+
|
136
|
+
" "+ex.message.chomp
|
137
|
+
return false
|
138
|
+
end
|
139
|
+
|
115
140
|
if response.code != "200"
|
141
|
+
puts "request_temp_token: Unsuccessful request, HTTP "+response.code+"."
|
116
142
|
return false
|
117
143
|
end
|
118
144
|
|
@@ -129,22 +155,41 @@ module Bandshell
|
|
129
155
|
return false
|
130
156
|
end
|
131
157
|
|
158
|
+
# If the temp token has been accepted, convert it into an auth token,
|
159
|
+
# which is saved in the config store.
|
160
|
+
# Returns success of the action of checking:
|
161
|
+
# stat_err on generic or unknown errors
|
162
|
+
# stat_serverr if the server is inaccessible or erroring
|
163
|
+
# stat_success if the acceptedness was reliably determined
|
132
164
|
def check_temp_token!
|
133
|
-
return
|
165
|
+
return :stat_err if temp_token.empty? #should not happen
|
134
166
|
|
135
167
|
query = URI.join(frontend_api_uri,"?screen_temp_token="+temp_token)
|
136
|
-
|
168
|
+
|
169
|
+
begin
|
170
|
+
response = Net::HTTP.get_response(query)
|
171
|
+
rescue StandardError => ex
|
172
|
+
puts "check_temp_token: Failed to access concerto server:\n"+
|
173
|
+
" "+ex.message.chomp
|
174
|
+
return :stat_serverr
|
175
|
+
end
|
176
|
+
|
137
177
|
if response.code != "200"
|
138
|
-
|
178
|
+
puts "check_temp_token: Unsuccessful request, HTTP "+response.code+"."
|
179
|
+
return :stat_serverr
|
139
180
|
end
|
140
181
|
|
141
182
|
data=JSON.parse(response.body)
|
142
183
|
if data.has_key? 'screen_auth_token'
|
143
184
|
ConfigStore.write_config('auth_token',data['screen_auth_token'])
|
144
185
|
ConfigStore.write_config('auth_temp_token','')
|
145
|
-
return
|
186
|
+
return :stat_success
|
187
|
+
elsif data.has_key? 'screen_temp_token'
|
188
|
+
# Indicates the API was accessed successfuly but the temp token
|
189
|
+
# has not been entered yet.
|
190
|
+
return :stat_success
|
146
191
|
end
|
147
|
-
return
|
192
|
+
return :stat_err
|
148
193
|
end
|
149
194
|
|
150
195
|
public
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'bandshell/config_store'
|
2
|
+
require 'net/http'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
# This class can be thought of as a singleton model which retrieves,
|
6
|
+
# manipulates, and stores information about the Player received from
|
7
|
+
# the Concerto server's concerto-hardware plugin. For example, it
|
8
|
+
# keeps track of screen on/off times.
|
9
|
+
module Bandshell
|
10
|
+
class PlayerInfo
|
11
|
+
attr_accessor :last_update
|
12
|
+
attr_accessor :on_off_rules
|
13
|
+
attr_accessor :shelf_life
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@last_update = Time.new(0)
|
17
|
+
@shelf_life = 60
|
18
|
+
@on_off_rules = [{"action"=>"on"}] # default to always-on
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns false on failure.
|
22
|
+
def update_if_stale
|
23
|
+
if (@last_update < Time.now - @shelf_life)
|
24
|
+
update
|
25
|
+
else
|
26
|
+
true
|
27
|
+
end
|
28
|
+
end #update
|
29
|
+
|
30
|
+
# Fetches the latest player settings from Concerto
|
31
|
+
# TODO: Store settings in BandshellConfig (and update whenever they have
|
32
|
+
# changed) so that configs are immediately available at boot.
|
33
|
+
# Returns true on success, false on failure.
|
34
|
+
def update
|
35
|
+
data = Bandshell::HardwareApi::get_player_info
|
36
|
+
if data.nil?
|
37
|
+
puts "update_player_info: Recieved null data from get_player_info!"
|
38
|
+
elsif data == :stat_serverr
|
39
|
+
puts "update_player_info: Server error while retrieving player info."
|
40
|
+
elsif data == :stat_badauth
|
41
|
+
puts "update_player_info: Auth error while retrieving player info."
|
42
|
+
else
|
43
|
+
new_rules = data['screen_on_off']
|
44
|
+
if new_rules.nil? or !new_rules.is_a? Array
|
45
|
+
puts "update_player_info: Invalid screen on/off rules received."
|
46
|
+
else
|
47
|
+
@on_off_rules = new_rules
|
48
|
+
@last_update = Time.now
|
49
|
+
return true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
return false
|
53
|
+
end #update
|
54
|
+
|
55
|
+
# Returns true if the screen should be turned on right now,
|
56
|
+
# according to the latest data recieved from concerto-hardware.
|
57
|
+
# Assumes on_off_rules is either nil or a valid ruleset.
|
58
|
+
# TODO: Evaluate effects of timezones
|
59
|
+
def screen_scheduled_on?
|
60
|
+
return true if on_off_rules.nil?
|
61
|
+
|
62
|
+
results = []
|
63
|
+
t = Time.now
|
64
|
+
on_off_rules.each do |rule|
|
65
|
+
rule_active = true
|
66
|
+
rule.each do |key, value|
|
67
|
+
case key
|
68
|
+
when "wkday"
|
69
|
+
rule_active = false unless value.include? t.wday.to_s
|
70
|
+
when "time_after"
|
71
|
+
rule_secs = seconds_since_midnight(Time.parse(value))
|
72
|
+
curr_secs = seconds_since_midnight(t)
|
73
|
+
rule_active = false unless curr_secs > rule_secs
|
74
|
+
when "time_before"
|
75
|
+
rule_secs = seconds_since_midnight(Time.parse(value))
|
76
|
+
curr_secs = seconds_since_midnight(t)
|
77
|
+
rule_active = false unless curr_secs < rule_secs
|
78
|
+
when "date"
|
79
|
+
day = Time.parse(value)
|
80
|
+
rule_active = false unless t.year==day.year and t.yday==day.yday
|
81
|
+
when "action"
|
82
|
+
# Do nothing.
|
83
|
+
else
|
84
|
+
# Do nothing.
|
85
|
+
# Err on the side of being on too long.
|
86
|
+
end # case key
|
87
|
+
end
|
88
|
+
if rule_active and rule.has_key? "action"
|
89
|
+
results << rule["action"]
|
90
|
+
end
|
91
|
+
end # each rule
|
92
|
+
|
93
|
+
if results.include? "force_on"
|
94
|
+
return true
|
95
|
+
elsif results.include? "off"
|
96
|
+
return false
|
97
|
+
elsif results.include? "on"
|
98
|
+
return true
|
99
|
+
else # All rules failed
|
100
|
+
return false
|
101
|
+
end
|
102
|
+
end #screen_scheduled_on?
|
103
|
+
|
104
|
+
# For a given time object, gives a numeric representation of the
|
105
|
+
# time of day on the day it represents.
|
106
|
+
def seconds_since_midnight(time)
|
107
|
+
time.sec + (time.min * 60) + (time.hour * 3600)
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
end #class
|
112
|
+
end #module
|
@@ -0,0 +1,159 @@
|
|
1
|
+
# This is a stateless class which provides a collection of methods
|
2
|
+
# for controlling the display. Currently supported control interfaces
|
3
|
+
# include:
|
4
|
+
# * DPMS
|
5
|
+
module Bandshell
|
6
|
+
class ScreenControl
|
7
|
+
class << self
|
8
|
+
# Only used if no display is set already.
|
9
|
+
def default_display
|
10
|
+
":0"
|
11
|
+
end
|
12
|
+
|
13
|
+
# Ensures that the display is in the specified state by enforcing
|
14
|
+
# each of a number of parameters, passed as the "state" hash.
|
15
|
+
# Valid keys:
|
16
|
+
# :on => (boolean value: true for on, false for off)
|
17
|
+
def enforce_screen_state(state)
|
18
|
+
if !state.is_a? Hash
|
19
|
+
raise "enforce_screen_state: did not receive a hash!"
|
20
|
+
end
|
21
|
+
if state.has_key? :on
|
22
|
+
if state[:on] == true
|
23
|
+
force_screen_on unless screen_is_on? == true
|
24
|
+
elsif state[:on] == false
|
25
|
+
force_screen_off unless screen_is_on? == false
|
26
|
+
else
|
27
|
+
raise "enforce_screen_state: Invalid value for :on!"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def force_screen_on
|
33
|
+
dpms_force_screen_on
|
34
|
+
end
|
35
|
+
|
36
|
+
def force_screen_off
|
37
|
+
dpms_force_screen_off
|
38
|
+
end
|
39
|
+
|
40
|
+
# true, false, or unknown
|
41
|
+
def screen_is_on?
|
42
|
+
dpms_screen_is_on?
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns a boolean and an explanatory string indicating
|
46
|
+
# whether the screen can be controlled by DPMS.
|
47
|
+
def control_availability
|
48
|
+
dpms_availability
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
#
|
54
|
+
# DPMS Implementation
|
55
|
+
#
|
56
|
+
# Note: this code relies on backtick system calls. These are dangerous,
|
57
|
+
# security-wise, so we need to ensure that no webserver-provided strings
|
58
|
+
# are interploated.
|
59
|
+
#
|
60
|
+
# We make no attempt to enable or disable DPMS. I'm not sure how the
|
61
|
+
# default is determined at a system level, but that may be an option
|
62
|
+
# if folks run into it being off.
|
63
|
+
|
64
|
+
# true, false, or :unknown
|
65
|
+
def dpms_screen_is_on?
|
66
|
+
if ENV['DISPLAY'].nil? or ENV['DISPLAY'].empty?
|
67
|
+
ENV['DISPLAY'] = default_display
|
68
|
+
end
|
69
|
+
|
70
|
+
begin
|
71
|
+
result = `xset -q 2>&1`
|
72
|
+
rescue Errno::ENOENT
|
73
|
+
return :unknown
|
74
|
+
end
|
75
|
+
if ($?.exitstatus != 0)
|
76
|
+
return :unknown
|
77
|
+
end
|
78
|
+
if result.include? "Monitor is On"
|
79
|
+
return true
|
80
|
+
elsif result.include? "Monitor is Off"
|
81
|
+
return false
|
82
|
+
else
|
83
|
+
return :unknown
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# true on success, false on failure.
|
88
|
+
def dpms_force_screen_on
|
89
|
+
if ENV['DISPLAY'].nil? or ENV['DISPLAY'].empty?
|
90
|
+
ENV['DISPLAY'] = default_display
|
91
|
+
end
|
92
|
+
|
93
|
+
begin
|
94
|
+
`xset dpms force on`
|
95
|
+
rescue Errno::ENOENT
|
96
|
+
return false
|
97
|
+
end
|
98
|
+
if $?.exitstatus != 0
|
99
|
+
return false
|
100
|
+
end
|
101
|
+
|
102
|
+
# Required if the screen was turned off with DPMS and the
|
103
|
+
# screensaver has not been disabled:
|
104
|
+
begin
|
105
|
+
`xset s reset`
|
106
|
+
rescue Errno::ENOENT #unlikely, but still...
|
107
|
+
return false
|
108
|
+
end
|
109
|
+
if $?.exitstatus != 0
|
110
|
+
return false
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# true on success, false on failure.
|
115
|
+
def dpms_force_screen_off
|
116
|
+
if ENV['DISPLAY'].nil? or ENV['DISPLAY'].empty?
|
117
|
+
ENV['DISPLAY'] = default_display
|
118
|
+
end
|
119
|
+
|
120
|
+
begin
|
121
|
+
`xset dpms force off`
|
122
|
+
rescue Errno::ENOENT
|
123
|
+
return false
|
124
|
+
end
|
125
|
+
if $?.exitstatus == 0
|
126
|
+
return true
|
127
|
+
else
|
128
|
+
return false
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def dpms_availability
|
133
|
+
if ENV['DISPLAY'].nil? or ENV['DISPLAY'].empty?
|
134
|
+
ENV['DISPLAY'] = default_display
|
135
|
+
end
|
136
|
+
|
137
|
+
begin
|
138
|
+
result = `xset -q 2>&1`
|
139
|
+
rescue Errno::ENOENT
|
140
|
+
return [false, "Can't access the xset command to control DPMS."]
|
141
|
+
end
|
142
|
+
if ($?.exitstatus == 127)
|
143
|
+
return [false, "Can't access the xset command to control DPMS."]
|
144
|
+
elsif ($?.exitstatus != 0)
|
145
|
+
# xset returns 1 and a message if the display is not specified or
|
146
|
+
# invalid.
|
147
|
+
return [false, "Problem running xset: "+result.chomp]
|
148
|
+
end
|
149
|
+
if result.include? "DPMS is Disabled"
|
150
|
+
return [false, "DPMS is disabled."]
|
151
|
+
elsif result.include? "DPMS is Enabled"
|
152
|
+
return [true, ""]
|
153
|
+
else
|
154
|
+
return [false, "Error parsing xset output."]
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end # self
|
158
|
+
end # ScreenControl
|
159
|
+
end
|