SwedishTV 0.0.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.
Files changed (4) hide show
  1. data/README +12 -0
  2. data/bin/tv +12 -0
  3. data/lib/swe_tv.rb +844 -0
  4. metadata +45 -0
data/README ADDED
@@ -0,0 +1,12 @@
1
+ = SwedishTV
2
+
3
+ This gem presents a comprehensive web view of the programs running on the
4
+ Swedish TV channels. Run it as:
5
+
6
+ tv [port]
7
+
8
+ This starts a web server on the specified port (default is 9901). Connect
9
+ to the web server to see the program tables.
10
+
11
+ Using the web interface you can select which channels you want to show
12
+ and highlight the programs you are interested in.
data/bin/tv ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ argv = ARGV
4
+
5
+ # Use this to test run program (when not run as gem)
6
+ if argv[0] == '--test'
7
+ argv[0,1] = []
8
+ $: << File.expand_path(File.join(__FILE__, "../../lib"))
9
+ end
10
+
11
+ require 'swe_tv'
12
+ start_tv_server(argv)
data/lib/swe_tv.rb ADDED
@@ -0,0 +1,844 @@
1
+ require 'webrick'
2
+ include WEBrick
3
+
4
+ #puts "Content-type: text/plain"
5
+ #puts
6
+
7
+ # ----------------------------------------------------------------------
8
+ # SERVER PART
9
+ # ----------------------------------------------------------------------
10
+
11
+ def start_webrick(config = {})
12
+ server = HTTPServer.new(config)
13
+ yield server if block_given?
14
+ %w(INT TERM).each {|signal| trap(signal) {server.shutdown}}
15
+ server.start
16
+ end
17
+
18
+ def start_tv_server(argv)
19
+ port = (argv[0] or 9901).to_i
20
+
21
+ config = {}
22
+ config[:Port] = port
23
+
24
+ start_webrick(config) {|server|
25
+ server.mount_proc("/tv") {|req, res|
26
+ res['content-type'] = 'text/html'
27
+ res['pragma'] = 'no-cache'
28
+
29
+ tv_app(req, res)
30
+ }
31
+
32
+ server.mount_proc("/") { |req, res|
33
+ res['content-type'] = 'text/html'
34
+ res.body = <<INDEX_END
35
+ <frameset rows=*,20%>
36
+ <frame src="tv" name=table>
37
+ <frame src="tv?program=2" name=info>
38
+ </frameset>
39
+ INDEX_END
40
+ }
41
+ }
42
+ end
43
+
44
+ # ----------------------------------------------------------------------
45
+ # APP PART
46
+ # ----------------------------------------------------------------------
47
+
48
+ require 'net/http'
49
+ require 'FileUtils'
50
+ require 'tmpdir'
51
+ require 'cgi'
52
+
53
+ $debug = false
54
+
55
+ USERFILE = File.expand_path(File.join(ENV['HOME'], "/.swe_tv"))
56
+
57
+ CACHEDIR = File.join(Dir.tmpdir, "swe_tv_cache")
58
+ FileUtils::mkdir(CACHEDIR) unless File.exist?(CACHEDIR)
59
+
60
+ CHANNELS = {
61
+ 'SVT1' =>21,
62
+ 'SVT2' =>22,
63
+ 'TV3' =>26,
64
+ 'TV4' =>27,
65
+ 'Kanal 5' =>10,
66
+ 'ZTV' =>42,
67
+ 'Via Act' => 28,
68
+ 'TV8' => 43,
69
+ 'TV 1000' => 33,
70
+ 'Cinema' => 34,
71
+ 'Canal+' => 11,
72
+ 'C+ Gul' =>12,
73
+ 'C+ Bla' =>55,
74
+ 'C+ Zap' =>140,
75
+ 'MTV' =>14,
76
+ 'MTV2' =>74,
77
+ 'Eurospo' =>9,
78
+ 'Via Spo' =>53,
79
+ 'Extreme' =>67,
80
+ 'CNN' => 5,
81
+ 'Eursp N' =>141,
82
+ 'Sky New' =>18,
83
+ 'CNBC' =>56,
84
+ 'NBC Eur' =>93,
85
+ '24' =>101,
86
+ 'SVT Eur' =>100,
87
+ 'TV4 Plu' =>142,
88
+ 'Barnkan' =>143,
89
+ 'ORT Int' => 139,
90
+ 'Discove' => 7,
91
+ 'Dis Civ' => 118,
92
+ 'Dis Sci' => 119,
93
+ 'Dis T&A' => 120,
94
+ 'Dis Mix' => 89,
95
+ 'Anim Pl' => 54,
96
+ 'Via Exp' =>76,
97
+ 'Travel' =>25,
98
+ 'Avante' =>68,
99
+ 'Nat Geo' =>48,
100
+ 'Cart Ne' =>4,
101
+ 'Fox Kid' =>60,
102
+ 'Nickelo' =>45,
103
+ 'Disney' =>147,
104
+ 'TCM' =>24,
105
+ 'The Stu' =>70,
106
+ 'Hallmar' =>13,
107
+ 'VH-1' =>47,
108
+ 'E!' =>114,
109
+ 'Style' =>49,
110
+ 'Fashion' =>82,
111
+ 'Club' =>115,
112
+ 'Reality' =>117,
113
+ 'God Cha' =>111,
114
+ 'Pink Pl' =>109,
115
+ 'Playboy' =>110,
116
+ 'Euronew' =>104,
117
+ 'HRT1' =>50,
118
+ 'TV Fin' =>29,
119
+ 'YLE TV1' =>31,
120
+ 'YLE TV2' =>36,
121
+ 'TV3 Fin' =>39,
122
+ 'TV Nor' =>73,
123
+ 'NRK1' =>72,
124
+ 'NRK2' =>63,
125
+ 'TV2 Nor' =>37,
126
+ 'TV3 Nor' =>62,
127
+ 'ZTV Nor' =>75,
128
+ 'TV Dan1' =>103,
129
+ 'TV Dan2' =>105,
130
+ 'DR1' =>58,
131
+ 'DR2' =>59,
132
+ 'TV2 Dan' =>71,
133
+ 'TV3 Dan' =>57,
134
+ 'TV2 Zul' =>126,
135
+ '3+ Dan' =>83,
136
+ 'BBC Pri' =>2,
137
+ 'BBC Wor' => 3,
138
+ 'MBC' => 88,
139
+ 'MDR' => 91,
140
+ 'NDR' => 92,
141
+ 'Viva' =>106,
142
+ 'Vox' =>107,
143
+ 'WDR' => 108,
144
+ 'Kabel 1' =>113,
145
+ 'Arte' =>84,
146
+ 'ORB' => 94,
147
+ 'PRO 7' => 98,
148
+ 'Sudwest' =>99,
149
+ 'Bayeris' =>81,
150
+ '3SAT' =>1,
151
+ 'ZDF' =>51,
152
+ 'ARD' =>69,
153
+ 'Deut We' =>6,
154
+ 'SAT1' =>17,
155
+ 'DSF' =>8,
156
+ 'RTL 2' =>15,
157
+ 'RTL' =>16,
158
+ 'Viva Pl' =>85,
159
+ 'TV Polo' =>66,
160
+ 'TV Chil' =>52,
161
+ 'TV5' =>40,
162
+ 'Mezzo' =>46,
163
+ 'Fran 5' =>86,
164
+ 'Rai Uno' =>64,
165
+ 'Rai Due' =>65,
166
+ 'TVE' =>41,
167
+ 'DTU7' =>80,
168
+ 'The Adu' =>87
169
+ }
170
+
171
+ def try_replace
172
+ begin
173
+ yield
174
+ rescue IndexError
175
+ end
176
+ end
177
+
178
+ # Fetch pages
179
+ class Fetch
180
+ def initialize(proxy = nil, proxyport = nil)
181
+ @proxy, @proxyport = proxy, proxyport
182
+ end
183
+
184
+ # Parse a network address into (host, port, path)
185
+ def parse_address(address, default_port = 80, default_path = '/')
186
+ address[/^http:\/\//] = ''
187
+ (host, path) = address.split('/', 2)
188
+ if path then path = '/' + path end
189
+ (host, port) = host.split(':', 2)
190
+ path = path || default_path
191
+ port = port || default_port
192
+ return [host, port, path]
193
+ end
194
+
195
+ # Download page at address
196
+ def get(address)
197
+ (host, port, path) = parse_address(address)
198
+ puts "getting #{host} #{port} #{path}" if $debug
199
+ Net::HTTP.version_1_1
200
+ begin
201
+ Net::HTTP.start(host, port.to_i, @proxy, @proxyport.to_i) do |http|
202
+ resp, body = http.get(path)
203
+ return body
204
+ end
205
+ rescue Exception
206
+ raise "Page unavailable: <a href='http://#{address}'>http://#{address}</a> (#{$!})"
207
+ end
208
+ end
209
+ end
210
+
211
+ # Keep track of time of programs
212
+ class ProgramTime
213
+ include Comparable
214
+
215
+ attr_reader :h, :m, :real
216
+
217
+ # 0-5 are considered night 5-0 morning
218
+ WRAPHOUR = 5
219
+
220
+ def ProgramTime.parse(s)
221
+ return ProgramTime.new(*(s.split(':').collect{|x| x.to_i}))
222
+ end
223
+
224
+ def initialize(h=0,m=0)
225
+ @h, @m = h, m
226
+ @real = to_s
227
+ @m = (@m/10)*10
228
+ if @h < WRAPHOUR
229
+ @h += 24
230
+ end
231
+ end
232
+
233
+ def -(time)
234
+ (@h - time.h - 1)*60 + (60 + @m - time.m)
235
+ end
236
+
237
+ def +(minutes)
238
+ h = @h
239
+ m = @m + minutes
240
+ h += m/60
241
+ m = m%60
242
+ return ProgramTime.new(h,m)
243
+ end
244
+
245
+ def to_s
246
+ sprintf("%02i:%02i", @h%24, m)
247
+ end
248
+
249
+ def <=>(time)
250
+ return -1 if @h < time.h
251
+ return 1 if @h > time.h
252
+ return -1 if @m < time.m
253
+ return 1 if @m > time.m
254
+ 0
255
+ end
256
+
257
+ def ProgramTime.now
258
+ t = Time.now
259
+ new(t.hour, t.min)
260
+ end
261
+ end
262
+
263
+ # Represents user preferences for display
264
+ class Preferences
265
+ STYLES = %w(regular yellow grey red green blue purple cyan)
266
+ attr_accessor :channels
267
+
268
+ def initialize
269
+ @style = {}
270
+ @channels = ['SVT1', 'SVT2', 'TV3', 'TV4', 'Kanal 5', 'ZTV']
271
+ end
272
+
273
+ def style(program)
274
+ @style[program.bare_name] || "regular"
275
+ end
276
+
277
+ def set_style(program, style)
278
+ if style == "regular"
279
+ @style.delete(program.bare_name)
280
+ else
281
+ @style[program.bare_name] = style
282
+ end
283
+ end
284
+ end
285
+
286
+ # Represents a program
287
+ class Program
288
+ attr_reader :start, :name, :desc, :showview, :id, :channel
289
+ attr_accessor :stop
290
+
291
+ FEATURELEN = 90
292
+ FEATURESTART = ProgramTime.new(20)
293
+
294
+ def Program.new_id
295
+ @id = 0 unless @id
296
+ @id = @id + 1
297
+ @id
298
+ end
299
+
300
+ def initialize(channel, start, name, desc, showview, stop=nil)
301
+ @channel = channel
302
+ @start = start
303
+ @name = name; @desc = desc; @showview = showview
304
+ @stop = stop
305
+ @id = CHANNELS[channel].to_s + '_' + Program.new_id.to_s
306
+ end
307
+
308
+ def duration; @stop - @start; end
309
+
310
+ def feature
311
+ @desc && @desc[/Regi:/]
312
+ end
313
+
314
+ def bare_name
315
+ bn = name.dup
316
+ try_replace {bn[/\(R\)/] = ''}
317
+ try_replace {bn[/,\s*forts/] = ''}
318
+ bn = $1 if bn[/(.*:)/]
319
+ bn.strip
320
+ end
321
+
322
+ def original_name
323
+ @desc =~ /.*\((.*)\)/ ? $1 : bare_name
324
+ end
325
+
326
+ # If this is a continuation of a program, returns that programs name
327
+ # otherwise nil.
328
+ def cont
329
+ return nil unless @name[/, forts$/]
330
+ cn = @name.dup
331
+ try_replace {cn[/, forts$/] = ''}
332
+ cn
333
+ end
334
+ end
335
+
336
+ # Represents a channel, a number of programs shown on a particular day. Also
337
+ # knows how to download the data for a channel.
338
+ class Channel
339
+ include Enumerable
340
+ attr_reader :channel
341
+
342
+ def Channel.new(day, channel, starttime, stoptime, flush = nil)
343
+ cache = "#{CACHEDIR}/#{channel}_" + day.strftime("%y%m%d")
344
+ if File.exists?(cache) && !flush
345
+ File.open(cache) {|f| return Marshal.load(f)}
346
+ else
347
+ chan = super(day, channel, starttime, stoptime)
348
+ File.open(cache, "w") {|f| Marshal.dump(chan, f)}
349
+ return chan
350
+ end
351
+ end
352
+
353
+ def initialize(day, channel, starttime, stoptime)
354
+ @day = day
355
+ @channel = channel
356
+ @channum = CHANNELS[@channel]
357
+ @programs = []
358
+ @starttime = starttime
359
+ @stoptime = stoptime
360
+ parse_table(get_table)
361
+ end
362
+
363
+ def each(*args, &pr)
364
+ @programs.each(*args, &pr)
365
+ end
366
+
367
+ def get_table
368
+ page = "http://www.dagenstv.com/se/chart/?cha=" + \
369
+ "#{@channum}&dat=" + @day.strftime("%Y-%m-%d")
370
+ Fetch.new.get(page)
371
+ end
372
+
373
+ def parse_table(tbl)
374
+ raise "Could not find table" unless tbl
375
+ last = nil
376
+
377
+
378
+ # Clear comments
379
+ tbl.gsub!(/<!--.*?-->/m, '')
380
+
381
+ # Match table rows
382
+ tbl.scan(/<tr>.*?(?=<tr>|<\/tr>)/m) do |row|
383
+ cellno = 0
384
+ start = name = desc = show = nil
385
+ row.scan(/<td[^>]*>.*?<\/td>/m) do |cell|
386
+ case cellno
387
+ when 0
388
+ start = cell[/[0-9]+:[0-9]+/]
389
+ when 2
390
+ if cell[/<span[^>]*class="stylecharteventname">(.*?)</m]
391
+ name = $1.strip
392
+ end
393
+ if cell[/<span[^>]*class="stylechartdescription">(.*?)<\/span>/m]
394
+ desc = $1.strip
395
+ end
396
+ if cell[/<span[^>]*class="stylechartshowview">(.*?)</m]
397
+ show = $1.strip
398
+ end
399
+ end
400
+ cellno += 1
401
+ end
402
+ if start && name
403
+ desc = desc || ""
404
+ start = ProgramTime.parse(start)
405
+ if start.between?(@starttime,@stoptime)
406
+ p = Program.new(channel, start, name, desc, show)
407
+ if last && last.start == start
408
+ @programs.pop
409
+ end
410
+ @programs << p
411
+ last.stop = start if last
412
+ last = p
413
+ end
414
+ end
415
+ end # tbl scan
416
+
417
+ if @programs.length > 0
418
+ # If first program does not start at starttime insert pseudoprogram
419
+ if @programs[0].start != @starttime
420
+ @programs.unshift Program.new(channel, @starttime, nil, nil, nil,
421
+ @programs[0].start)
422
+ end
423
+ lastend = @programs[-1].start + 60
424
+ if lastend >= @stoptime
425
+ @programs[-1].stop = @stoptime
426
+ else
427
+ @programs[-1].stop = lastend
428
+ @programs.push Program.new(channel, lastend, nil, nil, nil, @stoptime)
429
+ end
430
+ else
431
+ @programs << Program.new(channel, @starttime, nil, nil, nil,
432
+ @stoptime)
433
+ end
434
+ end
435
+
436
+ def to_s
437
+ s = @channel + "\n----------------------------------------\n\n" + \
438
+ @programs.collect! {|p| p.to_s}.join("\n\n")
439
+ end
440
+
441
+ # If this is a cont program returns the original program
442
+ def cont(program)
443
+ cn = program.cont
444
+ return nil unless cn
445
+ @programs.each {|p| return p if p.name == cn}
446
+ return nil
447
+ end
448
+ end
449
+
450
+ class Table
451
+ include Enumerable
452
+
453
+ attr_reader :day
454
+
455
+ START = ProgramTime.new(7)
456
+ STOP = ProgramTime.new(4)
457
+
458
+ def initialize(day, flush = nil, channels = CHANNELS.keys)
459
+ @day = day
460
+ @channels = channels.collect {|c| Channel.new(day, c, START, STOP, flush)}
461
+ end
462
+
463
+ def each(*args, &pr)
464
+ @channels.each(*args, &pr)
465
+ end
466
+
467
+ def start; START; end
468
+ def stop; STOP; end
469
+
470
+ def program_from_id(id)
471
+ program = nil
472
+ each {|c|
473
+ c.each{|p|
474
+ program = p if p.id == id
475
+ }
476
+ }
477
+ return program
478
+ end
479
+ end
480
+
481
+ class TableWriter
482
+ def initialize(table, prefs, id = nil)
483
+ @table, @prefs = table, prefs
484
+ @day = @table.day
485
+ @id = id
486
+ end
487
+
488
+ def write(out)
489
+ tomorrow = @day + 3600*24
490
+ yesterday = @day - 3600*24
491
+ scrollto = '#' + (if @id then @id.to_s else 'now' end)
492
+ out << <<-STOP
493
+ <html>
494
+ <head>
495
+ <title>Dagens TV-program</title>
496
+ <style>
497
+ td {font-size: 8pt; font-family: Verdana;}
498
+ .time {font-size: 7pt;}
499
+ .timeeven {font-size: 7pt; background: #cfc;}
500
+ .timenow {font-size: 7pt; background: #060; color: #fff;}
501
+
502
+ .regular {background: #fff;}
503
+ .yellow {background: #ffc;}
504
+ .grey {background: #ccc;}
505
+ .red {background: #fcc;}
506
+ .green {background: #cfc;}
507
+ .blue {background: #ccf;}
508
+ .purple {background: #fcf;}
509
+ .cyan {background: #cff;}
510
+ .feature {background: #fcc;}
511
+
512
+ .regularnow {background: #ada;}
513
+ .greynow {background: #9a9;}
514
+ .yellownow {background: #eeb;}
515
+ .rednow {background: #ebb;}
516
+ .greennow {background: #beb;}
517
+ .bluenow {background: #bbe;}
518
+ .purplenow {background: #ebe;}
519
+ .cyannow {background: #bee;}
520
+ .featurenow {background: #ebb;}
521
+
522
+ .programnow {font-size: 8pt; font-family: Verdana; background: #9c9;}
523
+
524
+ .head {font-size: 10pt; font-family: Verdana; font-weight: bold;
525
+ text-align: center}
526
+ h1 {text-align: center; color: #666633; font-family: arial}
527
+ a {color: #000000; text-decoration: none}
528
+ a.now {}
529
+ a.imdb {font-size: 7pt}
530
+ </style>
531
+ <script>
532
+ function showMessage(m) {
533
+ start = "<html> <head> <style> h1 {font-family: arial; font-size: 16; text-align: center} p.showview {color: red} </style> </head> <body bgcolor=white>"
534
+ stop = "</body></html>"
535
+
536
+ alert(start + m + stop)
537
+ }
538
+ </script>
539
+ <head>
540
+ <body bgcolor=white onload='self.location.href="#{scrollto}"'>
541
+ <h1>#{@day.strftime("%A %Y-%m-%d")}</h1>
542
+ <table width=100% align=center>
543
+ <tr>
544
+ <td>
545
+ <p align=left>
546
+ <a target=table href="tv?day=#{yesterday.strftime("%Y-%m-%d")}">&lt;&lt;
547
+ #{yesterday.strftime("%A %Y-%m-%d")}</a>
548
+ </p>
549
+ </td>
550
+ <td>
551
+ <p align=right>
552
+ <a target=table href="tv?day=#{tomorrow.strftime('%Y-%m-%d')}"
553
+ >#{tomorrow.strftime("%A %Y-%m-%d")} &gt;&gt;</a>
554
+ </p>
555
+ </td>
556
+ </tr>
557
+ <table bgcolor=#cccccc align=center>
558
+ <tr><td><table cellspacing=1 cellpadding=3 align=center>
559
+ STOP
560
+
561
+ out << '<tr>'
562
+ @table.each_with_index {|c,i|
563
+ if i%2==0
564
+ out << "<td class=head bgcolor=#ffffff>Tid</td>"
565
+ end
566
+ out << "<td class=head bgcolor=#ffffff>#{c.channel}</td>"
567
+ }
568
+ out << "<td class=head bgcolor=#ffffff>Tid</td>"
569
+ out << '</tr>'
570
+
571
+ now = ProgramTime.now
572
+ time = @table.start
573
+ while time < @table.stop
574
+ out << '<tr>'
575
+ style = 'time'
576
+ style = 'timeeven' if time.m == 0
577
+ isnow = false
578
+ if (now - time) >= 0 && (now-time) < 10
579
+ style = 'timenow'
580
+ isnow = true
581
+ end
582
+ @table.each_with_index {|c,i|
583
+ if i==0 && isnow
584
+ out << "<td class=#{style} bgcolor=#ffffff><a name=now>#{time}</a></td>"
585
+ elsif i%2==0
586
+ out << "<td class=#{style} bgcolor=#ffffff>#{time}</td>"
587
+ end
588
+ channel_row(out, c, time)
589
+ }
590
+ out << "<td class=#{style} bgcolor=#ffffff>#{time}</td>"
591
+ out << "</tr>\n"
592
+ time = time + 10
593
+ end
594
+ select_row(out)
595
+ out << "</table></td></tr></table>\n"
596
+ out << "</body>\n</html>\n"
597
+ end
598
+
599
+ def to_s
600
+ s = ''
601
+ write(s)
602
+ return s
603
+ end
604
+
605
+ def channel_row(out, channel, time)
606
+ match = channel.detect {|p| p.start == time}
607
+ if match
608
+ program_row(out, channel, match)
609
+ end
610
+ end
611
+
612
+ def select_row(out)
613
+ out << "<tr>"
614
+ channels = CHANNELS.keys.sort
615
+ channels << '--'
616
+ channels << 'Remove'
617
+ @prefs.channels.each_with_index {|c,i|
618
+ if i%2 == 0
619
+ out << <<-ADD
620
+ <td align=center>
621
+ </td>
622
+ ADD
623
+ end
624
+ out << <<-OPEN
625
+ <td align=center>
626
+ <form method=post>
627
+ <input type=hidden name=action value="select_channel">
628
+ <input type=hidden name=column value="#{i}">
629
+ <select name=channel onChange='submit()'>
630
+ OPEN
631
+ channels.each {|c2|
632
+ sel = c==c2 ? "selected" : ""
633
+ out << "<option #{sel} value='#{c2}'> #{c2}"
634
+ }
635
+ out << <<-CLOSE
636
+ </select>
637
+ </form>
638
+ </td>
639
+ CLOSE
640
+ }
641
+ out << <<-ADD
642
+ <td align=center>
643
+ <form method=post>
644
+ <input type=hidden name=action value="add_channel">
645
+ <input type=submit value='Add'>
646
+ </form>
647
+ </td>
648
+ </tr>
649
+ ADD
650
+ end
651
+
652
+ def program_style(channel, program)
653
+ orig_program = program
654
+ program = channel.cont(program) || program
655
+ style = if @prefs.style(program) != "regular"
656
+ @prefs.style(program)
657
+ elsif program.feature
658
+ "feature"
659
+ else
660
+ "regular"
661
+ end
662
+ if program_now(orig_program)
663
+ style + "now"
664
+ else
665
+ style
666
+ end
667
+ end
668
+
669
+ def program_now(program)
670
+ @now = @now || ProgramTime.now
671
+ @now >= program.start && @now < program.stop
672
+ end
673
+
674
+ def program_link(channel, program)
675
+ program = channel.cont(program) || program
676
+ klass = program_now(program) ? "class=now" : ""
677
+ daystr = @day.strftime("%Y-%m-%d")
678
+ %Q{<a name=#{program.id} href="tv?program=#{program.id}&day=#{daystr}" title="#{program.showview}"
679
+ target=info #{klass}>#{program.name}</a>}
680
+ end
681
+
682
+ def feature_link(channel, program)
683
+ program = channel.cont(program) || program
684
+ return "" unless program.feature
685
+ original_title = program.original_name
686
+ original_title = CGI.escape(original_title)
687
+ "<a class=imdb href='http://www.imdb.com/Find?#{original_title}'>(IMDB)</a>"
688
+ end
689
+
690
+ def program_row(out, channel, program)
691
+ rows = program.duration/10
692
+ if program.name
693
+ out << %Q{
694
+ <td class=#{program_style(channel, program)} valign=top bgcolor=#ffffff
695
+ rowspan=#{rows}>
696
+ #{program_link(channel, program)} #{feature_link(channel, program)}
697
+ </td>
698
+ }
699
+ else
700
+ out << "<td rowspan=#{rows}></td>"
701
+ end
702
+ end
703
+ end
704
+
705
+ class ProgramWriter
706
+ def initialize(table, prefs, id)
707
+ @prefs = prefs
708
+ @program = table.program_from_id(id)
709
+ @day = table.day
710
+ @id = id
711
+ end
712
+
713
+ def feature_link(program)
714
+ original_title = program.original_name
715
+ original_title_esc = CGI.escape(original_title)
716
+ "<a class=imdb href='http://www.imdb.com/Find?#{original_title_esc}'>IMDB: #{original_title}</a>"
717
+ end
718
+
719
+ def write(out)
720
+ unless @program
721
+ out << "<html></html>"
722
+ return
723
+ end
724
+ daystr = @day.strftime("%Y-%m-%d")
725
+ out << %Q{
726
+ <html><head>
727
+ <style>
728
+ p {font-size: 8pt; font-family: Verdana; margin: 0.3em}
729
+ body {font-size: 10}
730
+ h1 {font-family: arial; font-size: 12}
731
+ p.showview {color: red}
732
+ </style>
733
+ </head>
734
+ <body bgcolor=white>
735
+ <form target=table method=post>
736
+ <table width=100% cellspacing=10><tr>
737
+ <td valign=top width=50%>
738
+ <h1>#{@program.channel} #{@program.start.real} #{@program.name}
739
+ #{@program.showview}</h1>
740
+ <p>#{@program.desc}</p>
741
+ </td>
742
+ <td width=25% valign=top>
743
+ #{styles}
744
+ </td>
745
+
746
+ <td width=25%>
747
+ <input type=submit value="Update">
748
+ <input type=hidden name=action value=prefs>
749
+ <input type=hidden name=id value="#{@id}">
750
+ <input type=hidden name=day value="#{daystr}">
751
+ </td>
752
+ </form>
753
+ </body></html>
754
+ }
755
+ end
756
+
757
+ def styles
758
+ s = "<table><tr><td valign=top>"
759
+ Preferences::STYLES.each_with_index {|sty, i|
760
+ checked = (@prefs.style(@program) == sty) ? 'checked' : ''
761
+ if i%4 == 0 && i!=0
762
+ s << "</td><td>&nbsp;</td><td valign=top>"
763
+ end
764
+ s << "<p><input type=radio name=style value=#{sty} #{checked} onclick='submit()'> #{sty}</p>"
765
+ }
766
+ s << "</td></tr></table>"
767
+ return s
768
+ end
769
+
770
+ def to_s; s=''; write(s); s; end
771
+ end
772
+
773
+ def tv_app(req, res)
774
+ today = Time.now
775
+ if today.hour <=4
776
+ today = today - 3600*24
777
+ end
778
+
779
+ query = req.query
780
+
781
+ if query['day']
782
+ day = query['day']
783
+ daydata = day.split('-').collect {|s| s.to_i}
784
+ if daydata.length == 3
785
+ daydata << 12
786
+ today = Time.mktime(*daydata)
787
+ end
788
+ end
789
+
790
+ headers = {}
791
+
792
+ prefs = nil
793
+ if File.exists?(USERFILE)
794
+ File.open(USERFILE) {|f| prefs = Marshal.load(f)}
795
+ else
796
+ prefs = Preferences.new
797
+ end
798
+
799
+ if prefs.channels == nil
800
+ prefs.channels = ['SVT1', 'SVT2', 'TV3', 'TV4', 'Kanal 5', 'ZTV']
801
+ end
802
+
803
+ if query['program']
804
+ table = Table.new(today, false, prefs.channels)
805
+ program = query['program']
806
+ ProgramWriter.new(table, prefs, program).write(res.body)
807
+
808
+ elsif query['action'] == 'prefs'
809
+ table = Table.new(today, false, prefs.channels)
810
+ id = query['id'].to_s
811
+ program = table.program_from_id(id)
812
+ style = query['style']
813
+ if program
814
+ prefs.set_style(program, style)
815
+ File.open(USERFILE, "w") {|f| Marshal.dump(prefs, f)}
816
+ end
817
+ TableWriter.new(table, prefs, id).write(res.body)
818
+
819
+ elsif query['action'] == 'select_channel'
820
+ column = query['column'].to_i
821
+ program = query['channel']
822
+ if program == 'Remove'
823
+ if prefs.channels.length > 1
824
+ prefs.channels.delete_at(column)
825
+ end
826
+ else
827
+ prefs.channels[column] = program
828
+ end
829
+ File.open(USERFILE, "w") {|f| Marshal.dump(prefs, f)}
830
+ table = Table.new(today, false, prefs.channels)
831
+ TableWriter.new(table, prefs).write(res.body)
832
+
833
+ elsif query['action'] == 'add_channel'
834
+ prefs.channels << 'SVT1'
835
+ File.open(USERFILE, "w") {|f| Marshal.dump(prefs, f)}
836
+ table = Table.new(today, false, prefs.channels)
837
+ TableWriter.new(table, prefs).write(res.body)
838
+
839
+ else
840
+ table = Table.new(today, false, prefs.channels)
841
+ TableWriter.new(table, prefs).write(res.body)
842
+ end
843
+ end
844
+
metadata ADDED
@@ -0,0 +1,45 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11
3
+ specification_version: 1
4
+ name: SwedishTV
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.0.2
7
+ date: 2005-08-30 00:00:00 +02:00
8
+ summary: Shows Swedish TV channel schedules in web browser.
9
+ require_paths:
10
+ - lib
11
+ email: niklas@frykholm.se
12
+ homepage: http://www.frykholm.se/
13
+ rubyforge_project:
14
+ description:
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ -
22
+ - ">"
23
+ - !ruby/object:Gem::Version
24
+ version: 0.0.0
25
+ version:
26
+ platform: ruby
27
+ signing_key:
28
+ cert_chain:
29
+ authors:
30
+ - Niklas Frykholm
31
+ files:
32
+ - bin/CVS
33
+ - bin/tv
34
+ - lib/CVS
35
+ - lib/swe_tv.rb
36
+ - README
37
+ test_files: []
38
+ rdoc_options: []
39
+ extra_rdoc_files:
40
+ - README
41
+ executables:
42
+ - tv
43
+ extensions: []
44
+ requirements: []
45
+ dependencies: []