showoff 0.16.1 → 0.16.2
Sign up to get free protection for your applications and to get access to all the features.
- 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?
|