bandshell 0.9 → 1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|