SwedishTV 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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: []