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.
@@ -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