showoff 0.16.1 → 0.16.2
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/bin/showoff +17 -8
- data/lib/showoff.rb +250 -56
- data/lib/showoff/version.rb +1 -1
- data/public/css/presenter.css +7 -0
- data/public/css/showoff.css +93 -1
- data/public/js/presenter.js +21 -2
- data/public/js/showoff.js +67 -15
- data/views/header.erb +2 -0
- data/views/header_mini.erb +2 -0
- data/views/stats.erb +45 -54
- metadata +16 -3
- data/public/zoomline-0.0.1.html +0 -2085
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bcb7404f722269ce7b4c571c0a7d34c72f76d684
|
4
|
+
data.tar.gz: 7a2b4b825af14a88c58fccd9a851f51587894d9f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 180720b95f90f0948642258fca4c84327da441b85d7b23bb00203272ee05c7ed07c85af8665a5938c5c1b8ca683e98887aa1b98514ae4b59d32c9e8dde41af2e
|
7
|
+
data.tar.gz: 38036fa41718cf12953dd2db5a32cdc2b05840c11fea9652dfe8a525b0a095238a97bffbf643ddd5d4ea1ac57b0b2ce5af89929c7710761467e6323169dd3167
|
data/bin/showoff
CHANGED
@@ -4,6 +4,7 @@ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
|
4
4
|
require 'showoff'
|
5
5
|
require 'showoff/version'
|
6
6
|
require 'rubygems'
|
7
|
+
require 'fidget'
|
7
8
|
require 'gli'
|
8
9
|
|
9
10
|
# See https://github.com/davetron5000/gli/issues/196 for rationale for this silly wrapper
|
@@ -98,24 +99,24 @@ module Wrapper
|
|
98
99
|
arg_name 'heroku_name'
|
99
100
|
long_desc 'Creates the configuration files needed to Herokuize a Showoff presentation and then deploys it for you.'
|
100
101
|
command :heroku do |c|
|
101
|
-
|
102
|
+
|
102
103
|
c.desc 'add password protection to your heroku site'
|
103
104
|
c.flag [:p,:password]
|
104
|
-
|
105
|
+
|
105
106
|
c.desc 'force overwrite of existing Gemfile/.gems and config.ru files if they exist'
|
106
107
|
c.switch [:f,:force]
|
107
|
-
|
108
|
+
|
108
109
|
c.action do |global_options,options,args|
|
109
110
|
raise "heroku_name is required" if args.empty?
|
110
111
|
raise "Name must start with a letter and can only contain lowercase letters, numbers, and dashes." unless args.first =~ /^[a-z][a-z1-9-]*$/
|
111
|
-
|
112
|
+
|
112
113
|
unless system('git remote get-url heroku')
|
113
114
|
ShowOffUtils.command("heroku create #{args[0]}", "Please ensure that the heroku gem is installed and you're logged in.")
|
114
115
|
end
|
115
|
-
|
116
|
+
|
116
117
|
if ShowOffUtils.heroku(args[0],options[:f],options[:p])
|
117
118
|
ShowOffUtils.command('bundle install', 'Please ensure that the bundler gem is installed.')
|
118
|
-
|
119
|
+
|
119
120
|
begin
|
120
121
|
ShowOffUtils.command('git add Procfile Gemfile Gemfile.lock config.ru')
|
121
122
|
ShowOffUtils.command('git commit -m "Herokuized by Showoff"')
|
@@ -129,11 +130,11 @@ module Wrapper
|
|
129
130
|
puts
|
130
131
|
puts 'When done, please run "git push heroku master"'
|
131
132
|
end
|
132
|
-
|
133
|
+
|
133
134
|
if options[:p]
|
134
135
|
puts "CAREFUL: you are commiting your access password - anyone with read access to the repo can access the preso\n\n"
|
135
136
|
end
|
136
|
-
|
137
|
+
|
137
138
|
puts 'Your presentation has been Herokuized. Run `heroku open` to see it.'
|
138
139
|
end
|
139
140
|
end
|
@@ -159,6 +160,9 @@ module Wrapper
|
|
159
160
|
c.desc 'Disable content caching'
|
160
161
|
c.switch :nocache
|
161
162
|
|
163
|
+
c.desc 'Prevent the computer from sleeping during your presentation'
|
164
|
+
c.switch :nosleep
|
165
|
+
|
162
166
|
c.desc 'Port on which to run'
|
163
167
|
c.default_value "9090"
|
164
168
|
c.flag [:p, :port]
|
@@ -230,6 +234,11 @@ module Wrapper
|
|
230
234
|
|
231
235
|
"
|
232
236
|
|
237
|
+
if options[:nosleep] and Fidget.current_process
|
238
|
+
puts '**** System sleep has been suspended. ****'
|
239
|
+
puts
|
240
|
+
end
|
241
|
+
|
233
242
|
if options[:url]
|
234
243
|
ShowOffUtils.clone(options[:git_url], options[:git_branch], options[:git_path]) do
|
235
244
|
ShowOff.run!(options) do |server|
|
data/lib/showoff.rb
CHANGED
@@ -140,6 +140,7 @@ class ShowOff < Sinatra::Application
|
|
140
140
|
@section_major = 0
|
141
141
|
@section_minor = 0
|
142
142
|
@section_title = settings.showoff_config['name'] rescue 'Showoff Presentation'
|
143
|
+
@@slide_titles = [] # a list of generated slide names, used for cross references later.
|
143
144
|
|
144
145
|
@logger.debug settings.pres_template
|
145
146
|
|
@@ -161,12 +162,16 @@ class ShowOff < Sinatra::Application
|
|
161
162
|
begin
|
162
163
|
@@counter = JSON.parse(File.read("#{settings.statsdir}/#{settings.viewstats}"))
|
163
164
|
|
164
|
-
# port old format stats
|
165
|
+
# TODO: remove this logic 4/15/2017: port old format stats
|
165
166
|
unless @@counter.has_key? 'user_agents'
|
166
|
-
@@counter
|
167
|
+
@@counter['pageviews'] = @@counter
|
167
168
|
end
|
169
|
+
|
170
|
+
@@counter['current'] ||= {}
|
171
|
+
@@counter['pageviews'] ||= {}
|
172
|
+
@@counter['user_agents'] ||= {}
|
168
173
|
rescue
|
169
|
-
@@counter = { 'user_agents' => {}, 'pageviews' => {} }
|
174
|
+
@@counter = { 'user_agents' => {}, 'pageviews' => {}, 'current' => {} }
|
170
175
|
end
|
171
176
|
|
172
177
|
# keeps track of form responses. In memory to avoid concurrence issues.
|
@@ -395,11 +400,9 @@ class ShowOff < Sinatra::Application
|
|
395
400
|
|
396
401
|
# name the slide. If we've got multiple slides in this file, we'll have a sequence number
|
397
402
|
# include that sequence number to index directly into that content
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
content += "<div class=\"content #{classes}\" ref=\"#{name}\">\n"
|
402
|
-
end
|
403
|
+
ref = seq ? "#{name}:#{seq.to_s}" : name
|
404
|
+
content += "<div class=\"content #{classes}\" ref=\"#{ref}\">\n"
|
405
|
+
@@slide_titles << ref
|
403
406
|
|
404
407
|
# renderers like wkhtmltopdf needs an <h1> tag to use for a section title, but only when printing.
|
405
408
|
if opts[:print]
|
@@ -492,12 +495,6 @@ class ShowOff < Sinatra::Application
|
|
492
495
|
# Turn this into a document for munging
|
493
496
|
doc = Nokogiri::HTML::DocumentFragment.parse(result)
|
494
497
|
|
495
|
-
if opts[:section]
|
496
|
-
doc.css('div.notes-section').each do |section|
|
497
|
-
section.remove unless section.attr('class').split.include? opts[:section]
|
498
|
-
end
|
499
|
-
end
|
500
|
-
|
501
498
|
filename = File.join(settings.pres_dir, '_notes', "#{name}.md")
|
502
499
|
@logger.debug "personal notes filename: #{filename}"
|
503
500
|
if [nil, 'notes'].include? opts[:section] and File.file? filename
|
@@ -519,9 +516,68 @@ class ShowOff < Sinatra::Application
|
|
519
516
|
end
|
520
517
|
end
|
521
518
|
|
522
|
-
|
519
|
+
doc.css('.callout.glossary').each do |item|
|
520
|
+
next unless item.content =~ /^([^|]+)\|([^:]+):(.*)$/
|
521
|
+
item['data-term'] = $1
|
522
|
+
item['data-target'] = $2
|
523
|
+
item['data-text'] = $3
|
524
|
+
item.content = $3
|
525
|
+
|
526
|
+
glossary = (item.attr('class').split - ['callout', 'glossary']).first
|
527
|
+
address = glossary ? "#{glossary}/#{$2}" : $2
|
528
|
+
frag = "<a class=\"processed label\" href=\"glossary://#{address}\">#{$1}</a>"
|
529
|
+
|
530
|
+
item.children.before(Nokogiri::HTML::DocumentFragment.parse(frag))
|
531
|
+
end
|
532
|
+
|
533
|
+
# Process links
|
523
534
|
doc.css('a').each do |link|
|
524
|
-
|
535
|
+
next if link['href'].start_with? '#'
|
536
|
+
next if link['class'].split.include? 'processed' rescue nil
|
537
|
+
|
538
|
+
# If these are glossary links, populate the notes/handouts sections
|
539
|
+
if link['href'].start_with? 'glossary://'
|
540
|
+
doc.add_child '<div class="notes-section notes"></div>' if doc.css('div.notes-section.notes').empty?
|
541
|
+
doc.add_child '<div class="notes-section handouts"></div>' if doc.css('div.notes-section.handouts').empty?
|
542
|
+
|
543
|
+
term = link.content
|
544
|
+
text = link['title']
|
545
|
+
href = link['href']
|
546
|
+
href.slice!('glossary://')
|
547
|
+
|
548
|
+
parts = href.split('/')
|
549
|
+
target = parts.pop
|
550
|
+
name = parts.pop # either the glossary name or nil
|
551
|
+
|
552
|
+
link['class'] = 'term'
|
553
|
+
|
554
|
+
label = link.clone
|
555
|
+
label['class'] = 'label processed'
|
556
|
+
|
557
|
+
frag = Nokogiri::HTML::DocumentFragment.parse('<p></p>')
|
558
|
+
definition = frag.children.first
|
559
|
+
definition['class'] = "callout glossary #{name}"
|
560
|
+
definition['data-term'] = term
|
561
|
+
definition['data-target'] = target
|
562
|
+
definition['data-text'] = text
|
563
|
+
definition.content = text
|
564
|
+
definition.children.before(label)
|
565
|
+
|
566
|
+
[doc.css('div.notes-section.notes'), doc.css('div.notes-section.handouts')].each do |section|
|
567
|
+
section.first.add_child(definition.clone)
|
568
|
+
end
|
569
|
+
|
570
|
+
else
|
571
|
+
# Add a target so we open all external links from notes in a new window
|
572
|
+
link.set_attribute('target', '_blank')
|
573
|
+
end
|
574
|
+
end
|
575
|
+
|
576
|
+
# finally, remove any sections we don't want to print
|
577
|
+
if opts[:section]
|
578
|
+
doc.css('div.notes-section').each do |section|
|
579
|
+
section.remove unless section.attr('class').split.include? opts[:section]
|
580
|
+
end
|
525
581
|
end
|
526
582
|
|
527
583
|
doc.to_html
|
@@ -552,31 +608,87 @@ class ShowOff < Sinatra::Application
|
|
552
608
|
end
|
553
609
|
|
554
610
|
def process_content_for_all_slides(content, num_slides, opts={})
|
611
|
+
# this has to be text replacement for now, since the string can appear in any context
|
555
612
|
content.gsub!("~~~NUM_SLIDES~~~", num_slides.to_s)
|
613
|
+
doc = Nokogiri::HTML::DocumentFragment.parse(content)
|
556
614
|
|
557
615
|
# Should we build a table of contents?
|
558
616
|
if opts[:toc]
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
toc.add_child(entry)
|
568
|
-
|
569
|
-
link = Nokogiri::XML::Node.new('a', frag)
|
570
|
-
link['href'] = "##{section.parent.parent['id']}"
|
571
|
-
link.content = section.content
|
572
|
-
entry.add_child(link)
|
617
|
+
toc = Nokogiri::HTML::DocumentFragment.parse("<p id=\"toc\"></p>")
|
618
|
+
|
619
|
+
doc.css('div.subsection > h1:not(.section_title)').each do |section|
|
620
|
+
href = section.parent.parent['id']
|
621
|
+
frag = "<div class=\"tocentry\"><a href=\"##{href}\">#{section.content}</a></div>"
|
622
|
+
link = Nokogiri::HTML::DocumentFragment.parse(frag)
|
623
|
+
|
624
|
+
toc.children.first.add_child(link)
|
573
625
|
end
|
574
626
|
|
575
627
|
# swap out the tag, if found, with the table of contents
|
576
|
-
|
628
|
+
doc.at('p:contains("~~~TOC~~~")').replace(toc)
|
577
629
|
end
|
578
630
|
|
579
|
-
content
|
631
|
+
doc.css('.slide.glossary .content').each do |glossary|
|
632
|
+
name = (glossary.attr('class').split - ['content', 'glossary']).first
|
633
|
+
list = Nokogiri::HTML::DocumentFragment.parse('<ul class="glossary terms"></ul>')
|
634
|
+
seen = []
|
635
|
+
|
636
|
+
doc.css('.callout.glossary').each do |item|
|
637
|
+
target = (item.attr('class').split - ['callout', 'glossary']).first
|
638
|
+
|
639
|
+
# if the name matches or if we didn't name it to begin with.
|
640
|
+
next unless target == name
|
641
|
+
|
642
|
+
# the definition can exist in multiple places, so de-dup it here
|
643
|
+
term = item.attr('data-term')
|
644
|
+
next if seen.include? term
|
645
|
+
seen << term
|
646
|
+
|
647
|
+
# excrutiatingly find the parent slide content and grab the ref
|
648
|
+
# in a library less shitty, this would be something like
|
649
|
+
# $(this).parent().siblings('.content').attr('ref')
|
650
|
+
href = nil
|
651
|
+
item.ancestors('.slide').first.traverse do |element|
|
652
|
+
next if element['class'].nil?
|
653
|
+
next unless element['class'].split.include? 'content'
|
654
|
+
|
655
|
+
href = element.attr('ref').gsub('/', '_')
|
656
|
+
end
|
657
|
+
|
658
|
+
text = item.attr('data-text')
|
659
|
+
link = item.attr('data-target')
|
660
|
+
page = glossary.attr('ref')
|
661
|
+
anchor = "#{page}+#{link}"
|
662
|
+
next if href.nil? or text.nil? or link.nil?
|
663
|
+
|
664
|
+
frag = "<li><a id=\"#{anchor}\" class=\"label\">#{term}</a>#{text}<a href=\"##{href}\" class=\"return\">↩</a></li>"
|
665
|
+
item = Nokogiri::HTML::DocumentFragment.parse(frag)
|
666
|
+
|
667
|
+
list.children.first.add_child(item)
|
668
|
+
end
|
669
|
+
|
670
|
+
glossary.add_child(list)
|
671
|
+
end
|
672
|
+
|
673
|
+
# now fix all the links to point to the glossary page
|
674
|
+
doc.css('a').each do |link|
|
675
|
+
next if link['href'].nil?
|
676
|
+
next unless link['href'].start_with? 'glossary://'
|
677
|
+
|
678
|
+
href = link['href']
|
679
|
+
href.slice!('glossary://')
|
680
|
+
|
681
|
+
parts = href.split('/')
|
682
|
+
target = parts.pop
|
683
|
+
name = parts.pop # either the glossary name or nil
|
684
|
+
|
685
|
+
classes = name.nil? ? ".slide.glossary" : ".slide.glossary.#{name}"
|
686
|
+
href = doc.at("#{classes} .content").attr('ref') rescue nil
|
687
|
+
|
688
|
+
link['href'] = "##{href}+#{target}"
|
689
|
+
end
|
690
|
+
|
691
|
+
doc.to_html
|
580
692
|
end
|
581
693
|
|
582
694
|
# Find any lines that start with a <p>.(something), remove the ones tagged with
|
@@ -991,6 +1103,14 @@ class ShowOff < Sinatra::Application
|
|
991
1103
|
# Provide a button in the sidebar for interactive editing if configured
|
992
1104
|
@edit = settings.showoff_config['edit'] if @review
|
993
1105
|
|
1106
|
+
# store a cookie to tell clients apart. More reliable than using IP due to proxies, etc.
|
1107
|
+
unless request.cookies['client_id']
|
1108
|
+
@client_id = guid()
|
1109
|
+
response.set_cookie('client_id', @client_id)
|
1110
|
+
else
|
1111
|
+
@client_id = request.cookies['client_id']
|
1112
|
+
end
|
1113
|
+
|
994
1114
|
erb :index
|
995
1115
|
end
|
996
1116
|
|
@@ -1046,6 +1166,7 @@ class ShowOff < Sinatra::Application
|
|
1046
1166
|
|
1047
1167
|
# if we have a cache and we're not asking to invalidate it
|
1048
1168
|
return @@cache if (@@cache and params['cache'] != 'clear')
|
1169
|
+
@@slide_titles = []
|
1049
1170
|
content = get_slides_html(:static=>static)
|
1050
1171
|
|
1051
1172
|
# allow command line cache disabling
|
@@ -1081,12 +1202,79 @@ class ShowOff < Sinatra::Application
|
|
1081
1202
|
erb :download
|
1082
1203
|
end
|
1083
1204
|
|
1205
|
+
def stats_data()
|
1206
|
+
data = {}
|
1207
|
+
begin
|
1208
|
+
|
1209
|
+
# what are viewers looking at right now?
|
1210
|
+
now = Time.now.to_i # let's throw away viewers who haven't done anything in 5m
|
1211
|
+
active = @@counter['current'].select {|client, view| (now - view[1]).abs < 300 }
|
1212
|
+
|
1213
|
+
# percentage of stray viewers
|
1214
|
+
stray = active.select {|client, view| view[0] != @@current[:name] }
|
1215
|
+
stray_p = ((stray.size.to_f / active.size.to_f) * 100).to_i rescue 0
|
1216
|
+
data['stray_p'] = stray_p
|
1217
|
+
|
1218
|
+
# percentage of idle viewers
|
1219
|
+
idle = @@counter['current'].size - active.size
|
1220
|
+
idle_p = ((idle.to_f / @@counter['current'].size.to_f) * 100).to_i rescue 0
|
1221
|
+
data['idle_p'] = idle_p
|
1222
|
+
|
1223
|
+
viewers = @@slide_titles.map do |slide|
|
1224
|
+
count = active.select {|client, view| view[0] == slide }.size
|
1225
|
+
flags = (slide == @@current[:name]) ? 'current' : nil
|
1226
|
+
[count, slide, nil, flags]
|
1227
|
+
end
|
1228
|
+
|
1229
|
+
# trim the ends, if nobody's looking we don't much care.
|
1230
|
+
viewers.pop while viewers.last[0] == 0
|
1231
|
+
viewers.shift while viewers.first[0] == 0
|
1232
|
+
viewmax = viewers.max_by {|view| view[0] }.first
|
1233
|
+
|
1234
|
+
data['viewers'] = viewers
|
1235
|
+
data['viewmax'] = viewmax
|
1236
|
+
rescue => e
|
1237
|
+
@logger.warn "Not enough data to generate pageviews."
|
1238
|
+
@logger.debug e.message
|
1239
|
+
@logger.debug e.backtrace.first
|
1240
|
+
end
|
1241
|
+
|
1242
|
+
begin
|
1243
|
+
# current elapsed time for the zoomline view
|
1244
|
+
elapsed = @@slide_titles.map do |slide|
|
1245
|
+
if @@counter['pageviews'][slide].nil?
|
1246
|
+
time = 0
|
1247
|
+
else
|
1248
|
+
time = @@counter['pageviews'][slide].inject(0) do |outer, (viewer, views)|
|
1249
|
+
outer += views.inject(0) { |inner, view| inner += view['elapsed'] }
|
1250
|
+
end
|
1251
|
+
end
|
1252
|
+
string = Time.at(time).gmtime.strftime('%M:%S')
|
1253
|
+
flags = (slide == @@current[:name]) ? 'current' : nil
|
1254
|
+
|
1255
|
+
[ time, slide, string, flags ]
|
1256
|
+
end
|
1257
|
+
maxtime = elapsed.max_by {|view| view[0] }.first
|
1258
|
+
|
1259
|
+
data['elapsed'] = elapsed
|
1260
|
+
data['maxtime'] = maxtime
|
1261
|
+
rescue => e
|
1262
|
+
# expected if this is loaded before a presentation has been compiled
|
1263
|
+
@logger.warn "Not enough data to generate elapsed time."
|
1264
|
+
@logger.debug e.message
|
1265
|
+
@logger.debug e.backtrace.first
|
1266
|
+
end
|
1267
|
+
|
1268
|
+
data.to_json
|
1269
|
+
end
|
1270
|
+
|
1084
1271
|
def stats()
|
1085
|
-
if
|
1272
|
+
if localhost?
|
1086
1273
|
# the presenter should have full stats in the erb
|
1087
1274
|
@counter = @@counter['pageviews']
|
1088
1275
|
end
|
1089
1276
|
|
1277
|
+
# for the full page view. Maybe to be disappeared
|
1090
1278
|
@all = Hash.new
|
1091
1279
|
@@counter['pageviews'].each do |slide, stats|
|
1092
1280
|
@all[slide] = 0
|
@@ -1095,10 +1283,6 @@ class ShowOff < Sinatra::Application
|
|
1095
1283
|
end
|
1096
1284
|
end
|
1097
1285
|
|
1098
|
-
# most and least five viewed slides
|
1099
|
-
@least = @all.sort_by {|slide, time| time}[0..4]
|
1100
|
-
@most = @all.sort_by {|slide, time| -time}[0..4]
|
1101
|
-
|
1102
1286
|
erb :stats
|
1103
1287
|
end
|
1104
1288
|
|
@@ -1131,7 +1315,7 @@ class ShowOff < Sinatra::Application
|
|
1131
1315
|
end
|
1132
1316
|
|
1133
1317
|
|
1134
|
-
|
1318
|
+
def self.do_static(args, opts = {})
|
1135
1319
|
args ||= [] # handle nil arguments
|
1136
1320
|
what = args[0] || "index"
|
1137
1321
|
opt = args[1]
|
@@ -1319,18 +1503,20 @@ class ShowOff < Sinatra::Application
|
|
1319
1503
|
(0..15).to_a.map{|a| rand(16).to_s(16)}.join
|
1320
1504
|
end
|
1321
1505
|
|
1322
|
-
def
|
1506
|
+
def valid_presenter_cookie?
|
1507
|
+
return false if @@cookie.nil?
|
1323
1508
|
(request.cookies['presenter'] == @@cookie)
|
1324
1509
|
end
|
1325
1510
|
|
1326
1511
|
post '/form/:id' do |id|
|
1327
|
-
|
1512
|
+
client_id = request.cookies['client_id']
|
1513
|
+
@logger.warn("Saving form answers from ip:#{request.ip} with ID of #{client_id} for id:##{id}")
|
1328
1514
|
|
1329
1515
|
form = params.reject { |k,v| ['splat', 'captures', 'id'].include? k }
|
1330
1516
|
|
1331
1517
|
# make sure we've got a bucket for this form, then save our answers
|
1332
1518
|
@@forms[id] ||= {}
|
1333
|
-
@@forms[id][
|
1519
|
+
@@forms[id][client_id] = form
|
1334
1520
|
|
1335
1521
|
form.to_json
|
1336
1522
|
end
|
@@ -1445,13 +1631,13 @@ class ShowOff < Sinatra::Application
|
|
1445
1631
|
begin
|
1446
1632
|
control = JSON.parse(data)
|
1447
1633
|
|
1448
|
-
@logger.
|
1634
|
+
@logger.debug "#{control.inspect}"
|
1449
1635
|
|
1450
1636
|
case control['message']
|
1451
1637
|
when 'update'
|
1452
1638
|
# websockets don't use the same auth standards
|
1453
1639
|
# we use a session cookie to identify the presenter
|
1454
|
-
if
|
1640
|
+
if valid_presenter_cookie?
|
1455
1641
|
name = control['name']
|
1456
1642
|
slide = control['slide'].to_i
|
1457
1643
|
increment = control['increment'].to_i rescue 0
|
@@ -1472,29 +1658,37 @@ class ShowOff < Sinatra::Application
|
|
1472
1658
|
|
1473
1659
|
when 'register'
|
1474
1660
|
# save a list of presenters
|
1475
|
-
if
|
1661
|
+
if valid_presenter_cookie?
|
1476
1662
|
remote = request.env['REMOTE_HOST'] || request.env['REMOTE_ADDR']
|
1477
1663
|
settings.presenters << ws
|
1478
1664
|
@logger.warn "Registered new presenter: #{remote}"
|
1479
1665
|
end
|
1480
1666
|
|
1481
1667
|
when 'track'
|
1482
|
-
remote =
|
1668
|
+
remote = valid_presenter_cookie? ? 'presenter' : request.cookies['client_id']
|
1483
1669
|
slide = control['slide']
|
1484
|
-
time = control['time'].to_f
|
1485
1670
|
|
1486
|
-
|
1487
|
-
|
1671
|
+
if control.has_key? 'time'
|
1672
|
+
time = control['time'].to_f
|
1673
|
+
|
1674
|
+
# record the UA of the client if we haven't seen it before
|
1675
|
+
@@counter['user_agents'][remote] ||= request.user_agent
|
1488
1676
|
|
1489
|
-
|
1490
|
-
|
1491
|
-
|
1492
|
-
|
1493
|
-
|
1494
|
-
|
1495
|
-
|
1677
|
+
views = @@counter['pageviews']
|
1678
|
+
# a bucket for this slide
|
1679
|
+
views[slide] ||= Hash.new
|
1680
|
+
# a bucket of slideviews for this address
|
1681
|
+
views[slide][remote] ||= Array.new
|
1682
|
+
# and add this slide viewing to the bucket
|
1683
|
+
views[slide][remote] << { 'elapsed' => time, 'timestamp' => Time.now.to_i, 'presenter' => @@current[:name] }
|
1684
|
+
|
1685
|
+
@logger.debug "Logged #{time} on slide #{slide} for #{remote}"
|
1686
|
+
|
1687
|
+
else
|
1688
|
+
@@counter['current'][remote] = [slide, Time.now.to_i]
|
1689
|
+
@logger.debug "Recorded current slide #{slide} for #{remote}"
|
1690
|
+
end
|
1496
1691
|
|
1497
|
-
@logger.debug "Logged #{time} on slide #{slide} for #{remote}"
|
1498
1692
|
|
1499
1693
|
when 'position'
|
1500
1694
|
ws.send( { 'current' => @@current[:number] }.to_json ) unless @@cookie.nil?
|