bandshell 0.9 → 1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ &quot;New Screen&quot; 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="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.js"></script>
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 status: Scheduled
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
- Bandshell::ConfigStore.read_config('concerto_url', '')
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 Errno::ECONNREFUSED
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
- response = Net::HTTP.get_response(frontend_api_uri)
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 false if temp_token.empty?
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
- response = Net::HTTP.get_response(query)
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
- return false
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 true
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 false
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