xmltv 0.8.1
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.
- data/History.txt +5 -0
- data/Manifest.txt +17 -0
- data/README.txt +123 -0
- data/Rakefile +18 -0
- data/bin/xmltv +13 -0
- data/lib/xmltv/sample/dumpids.rb +15 -0
- data/lib/xmltv/sample/mythtv_chns.yaml +183 -0
- data/lib/xmltv/sample/sample_output +73 -0
- data/lib/xmltv/sample/tvcat_spoolfiles.rb +29 -0
- data/lib/xmltv/sites/film1.rb +100 -0
- data/lib/xmltv/sites/rt.rb +133 -0
- data/lib/xmltv/sites/trivial.rb +33 -0
- data/lib/xmltv/sites/tvgids.rb +224 -0
- data/lib/xmltv/sites/tvtoday.rb +185 -0
- data/lib/xmltv/sites/upc.rb +157 -0
- data/lib/xmltv/sites/vpro.rb +122 -0
- data/lib/xmltv/xmltv.rb +737 -0
- metadata +89 -0
data/lib/xmltv/xmltv.rb
ADDED
@@ -0,0 +1,737 @@
|
|
1
|
+
#!/usr/bin/ruby -w
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'hpricot'
|
5
|
+
require 'rexml/document'
|
6
|
+
require 'yaml'
|
7
|
+
require 'date'
|
8
|
+
require 'fileutils'
|
9
|
+
require 'timeout'
|
10
|
+
require 'open-uri'
|
11
|
+
require 'pp'
|
12
|
+
require 'iconv'
|
13
|
+
require 'set'
|
14
|
+
require 'optparse'
|
15
|
+
require 'optparse/time'
|
16
|
+
require 'ostruct'
|
17
|
+
|
18
|
+
class Time
|
19
|
+
public :to_date
|
20
|
+
end
|
21
|
+
module MailSender
|
22
|
+
def linuxmail(to, subj, body)
|
23
|
+
from = `/usr/bin/id --name --user`.chomp
|
24
|
+
header = ["To: #{to}", "From: #{from}", "Subject: #{subj}"].join("\n")
|
25
|
+
mess = [header,"", body].join("\n")
|
26
|
+
puts mess
|
27
|
+
IO.popen("/usr/sbin/sendmail -t", 'w') do |h|
|
28
|
+
h.puts mess
|
29
|
+
end
|
30
|
+
end
|
31
|
+
def sendmail(*args)
|
32
|
+
if test(?x, '/usr/sbin/sendmail')
|
33
|
+
linuxmail(*args)
|
34
|
+
else
|
35
|
+
raise "FIXME: Don't know how to send mail"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
module_function :sendmail, :linuxmail
|
39
|
+
end
|
40
|
+
class String
|
41
|
+
UTF = 'utf-8'
|
42
|
+
ISO = 'iso-8859-15'
|
43
|
+
TO_UTF = Iconv.new(UTF, ISO)
|
44
|
+
CK_UTF = Iconv.new(UTF, UTF)
|
45
|
+
def to_utf
|
46
|
+
TO_UTF.iconv(self)
|
47
|
+
end
|
48
|
+
def ck_utf
|
49
|
+
CK_UTF.iconv(self)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class Date
|
54
|
+
Maanden = [nil] + %w{ januari februari maart april mei juni juli augustus september oktober november december }
|
55
|
+
Dagen = %w{ zondag maandag dinsdag woensdag donderdag vrijdag zaterdag }
|
56
|
+
def self.dutch(instring)
|
57
|
+
nu = today
|
58
|
+
defjaar, defmaand, defdag, weekdag = nu.year, nu.month, nu.day, nu.wday
|
59
|
+
jaar = maand = dag = nil
|
60
|
+
string = instring.gsub(/[[:cntrl:][:punct:]]/, '').downcase
|
61
|
+
string.split.each do |elem|
|
62
|
+
case elem
|
63
|
+
when /[[:alpha:]]+/
|
64
|
+
if Maanden.include?(elem)
|
65
|
+
maand = Maanden.index(elem)
|
66
|
+
if maand < nu.month
|
67
|
+
defjaar += 1
|
68
|
+
end
|
69
|
+
elsif Dagen.include?(elem)
|
70
|
+
wd = Dagen.index(elem) - weekdag
|
71
|
+
wd += 7 if wd < 0
|
72
|
+
nu += wd
|
73
|
+
defjaar, defmaand, defdag, weekdag = nu.year, nu.month, nu.day, nu.wday
|
74
|
+
end
|
75
|
+
when /[[:digit:]]+/
|
76
|
+
ent = elem.to_i
|
77
|
+
if ent <= 31
|
78
|
+
dag = ent
|
79
|
+
elsif ent > 1900
|
80
|
+
jaar = ent
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
new(jaar || defjaar, maand || defmaand,dag || defdag)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
module XMLTV
|
90
|
+
class ValidateError < Exception; end
|
91
|
+
class BadChannelError < Exception; end
|
92
|
+
class BadSiteError < Exception; end
|
93
|
+
class Progdata < Hash
|
94
|
+
def self.new
|
95
|
+
super {|h,v| h[v] = Hash.new }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
VERSION = '0.8.1'
|
99
|
+
Progdir = ($LOAD_PATH.find {|x| test(?f, "#{x}/xmltv/xmltv.rb")} || 'lib') + '/xmltv/sites'
|
100
|
+
Sites = Dir["#{Progdir}/*.rb"].map{|x| x[0..-4]}.map{|x| File.basename(x)}
|
101
|
+
class XmltvOptparser
|
102
|
+
|
103
|
+
#
|
104
|
+
# Return a structure describing the options.
|
105
|
+
#
|
106
|
+
def self.parse(args)
|
107
|
+
options = OpenStruct.new
|
108
|
+
options.basedir = "#{ENV['HOME']}/.xmltv"
|
109
|
+
options.validate = true
|
110
|
+
|
111
|
+
opts = OptionParser.new do |opts|
|
112
|
+
opts.banner = "Usage: #{$PROGRAM_NAME} [options]"
|
113
|
+
|
114
|
+
opts.separator ""
|
115
|
+
opts.separator "Specific options:"
|
116
|
+
|
117
|
+
opts.on("-a", "--list-all",
|
118
|
+
"List all available channels") do |av|
|
119
|
+
options.available = av
|
120
|
+
options.action = true
|
121
|
+
end
|
122
|
+
|
123
|
+
opts.on("--add x,y,z", Array, "Add channels to config") do |list|
|
124
|
+
options.add = list
|
125
|
+
options.action = true
|
126
|
+
end
|
127
|
+
opts.on("--delete x,y,z", Array, "Delete channels from config") do |list|
|
128
|
+
options.del = list
|
129
|
+
options.action = true
|
130
|
+
end
|
131
|
+
opts.on("-l", "--list", "List configured channels") do |list|
|
132
|
+
options.list = list
|
133
|
+
options.action = true
|
134
|
+
end
|
135
|
+
opts.on("-c", "--config-file FILENAME", "Configuration file") do |list|
|
136
|
+
options.config = list
|
137
|
+
end
|
138
|
+
opts.on( "--basedir DIRNAME", "Basedir (#{options.basedir})") do |list|
|
139
|
+
options.basedir = list
|
140
|
+
end
|
141
|
+
opts.on( "--no-validate", "Do not validate") do |v|
|
142
|
+
options.validate = false
|
143
|
+
end
|
144
|
+
opts.on( "--mailto USER", "Mail exception") do |user|
|
145
|
+
options.mailto = user
|
146
|
+
end
|
147
|
+
|
148
|
+
opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
|
149
|
+
options.verbose = v
|
150
|
+
end
|
151
|
+
|
152
|
+
opts.separator ""
|
153
|
+
opts.separator "Common options:"
|
154
|
+
|
155
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
156
|
+
STDERR.puts opts
|
157
|
+
exit
|
158
|
+
end
|
159
|
+
|
160
|
+
opts.on_tail("--version", "Show version") do
|
161
|
+
options.version = true
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
opts.parse!(args)
|
166
|
+
options
|
167
|
+
end
|
168
|
+
end
|
169
|
+
XmltvOptions = XmltvOptparser.parse(ARGV)
|
170
|
+
|
171
|
+
|
172
|
+
class XmlWriter
|
173
|
+
With_lang = [ 'title','sub-title','desc','category','language','orig-language','country','premiere','last-chance']
|
174
|
+
Programme_dtd = [
|
175
|
+
['title' ],
|
176
|
+
[ 'sub-title' ],
|
177
|
+
[ 'desc' ],
|
178
|
+
[ 'credits', nil, [
|
179
|
+
'director',
|
180
|
+
'actor',
|
181
|
+
'writer',
|
182
|
+
'adapter',
|
183
|
+
'producer',
|
184
|
+
'presenter',
|
185
|
+
'commentator',
|
186
|
+
'guest'
|
187
|
+
]
|
188
|
+
],
|
189
|
+
['date'],
|
190
|
+
['category' ] ,
|
191
|
+
['language' ] ,
|
192
|
+
['orig-language' ] ,
|
193
|
+
[ 'length', [ 'units' ] ],
|
194
|
+
[ 'icon', [ 'src', 'width', 'height' ] ],
|
195
|
+
[ 'url'],
|
196
|
+
[ 'country' ] ,
|
197
|
+
[ 'episode-num', [ 'system' ] ],
|
198
|
+
[ 'video', [], [
|
199
|
+
'present',
|
200
|
+
'colour',
|
201
|
+
'aspect',
|
202
|
+
'quality'
|
203
|
+
]
|
204
|
+
],
|
205
|
+
[ 'audio' , [], [
|
206
|
+
'present',
|
207
|
+
'stereo'
|
208
|
+
]
|
209
|
+
],
|
210
|
+
['previously-shown', ['start', 'channel' ]],
|
211
|
+
['premiere' ],
|
212
|
+
[ 'last-chance' ],
|
213
|
+
[ 'new' ],
|
214
|
+
[ 'subtitles', [ 'type' ], [ 'language' ] ],
|
215
|
+
[ 'rating', [ 'system' ] , [ 'value', 'icon' ] ],
|
216
|
+
[ 'star-rating', [], [ 'value', 'icon' ] ]
|
217
|
+
]
|
218
|
+
def initialize(grabber)
|
219
|
+
@grabber = grabber
|
220
|
+
@doc = doc = REXML::Document.new
|
221
|
+
doc << REXML::XMLDecl.new("1.0", "UTF-8")
|
222
|
+
dtd = grabber.config['dtd'] || 'file:///usr/share/xmltv/xmltv.dtd'
|
223
|
+
doc << REXML::DocType.new('tv', %Q{SYSTEM "#{dtd}"})
|
224
|
+
@el_tv = doc.add_element( 'tv' )
|
225
|
+
@el_tv.attributes['generator-info-name'] = 'xmltv.rb'
|
226
|
+
end
|
227
|
+
def add_dtd(progdata)
|
228
|
+
raise ArgumentError.new("add_dtd must have a Progdata") unless progdata.is_a?(Progdata)
|
229
|
+
p = @el_tv.add_element('programme')
|
230
|
+
%w{ start stop }.each do |word|
|
231
|
+
p.attributes[word] = progdata[word].strftime( '%Y%m%d%H%M%S %Z')
|
232
|
+
end
|
233
|
+
p.attributes['channel'] = progdata['channel']
|
234
|
+
Programme_dtd.each do |el|
|
235
|
+
name , attrs , elements = el
|
236
|
+
# STDERR.puts name
|
237
|
+
next if (cur = progdata[name]).empty?
|
238
|
+
el = p.add_element(name)
|
239
|
+
el.text = cur if cur.is_a? String
|
240
|
+
if With_lang.include? name
|
241
|
+
el.attributes['lang'] = @grabber.lang
|
242
|
+
end
|
243
|
+
if attrs
|
244
|
+
attrs.each do |a|
|
245
|
+
el.attributes[a] = cur[a] if cur[a]
|
246
|
+
end
|
247
|
+
end
|
248
|
+
if elements
|
249
|
+
elements.each do |e|
|
250
|
+
case cur[e]
|
251
|
+
when String
|
252
|
+
el.add_element(e).text = cur[e]
|
253
|
+
when Array
|
254
|
+
cur[e].each do |r|
|
255
|
+
el.add_element(e).text = r
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
def write_file(pda, chan_id)
|
263
|
+
pda.each do |progdata|
|
264
|
+
begin
|
265
|
+
add_dtd(progdata)
|
266
|
+
rescue StandardError => exc
|
267
|
+
STDERR.puts exc, exc.message, exc.backtrace, '===='
|
268
|
+
PP.pp(progdata, STDERR)
|
269
|
+
raise
|
270
|
+
end
|
271
|
+
end
|
272
|
+
file = ">#{@grabber.outputfile(chan_id)}"
|
273
|
+
errorfile = '/var/tmp/xmllint-errors'
|
274
|
+
if XmltvOptions.validate
|
275
|
+
IO.popen(" ( xmllint --valid - | tv_sort #{file} ) 2>#{errorfile}", 'w') do |h|
|
276
|
+
write_xml h
|
277
|
+
end
|
278
|
+
if test(?s, errorfile)
|
279
|
+
raise ValidateError.new("zie #{errorfile}")
|
280
|
+
end
|
281
|
+
else
|
282
|
+
File.open("#{@grabber.outputfile(chan_id)}", 'w') do |h|
|
283
|
+
write_xml h
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
def write_xml(h, indent = -1)
|
288
|
+
@doc.write(h, indent)
|
289
|
+
h.puts
|
290
|
+
end
|
291
|
+
|
292
|
+
end
|
293
|
+
class Grabber
|
294
|
+
Dag = 24 * 60 * 60
|
295
|
+
Vandaag = Date.today
|
296
|
+
attr_accessor :myname, :chnbasedir, :spooldir, :channel_list, :lang, :generator, :base_url, :config_file_name
|
297
|
+
attr_accessor :config, :all_channels, :reject_file_name
|
298
|
+
mtv =
|
299
|
+
begin
|
300
|
+
YAML.load_file("#{XmltvOptions.basedir}/mythtv_chns.yaml")
|
301
|
+
rescue Errno::ENOENT
|
302
|
+
Hash.new
|
303
|
+
end
|
304
|
+
MythTV = Hash.new
|
305
|
+
mtv.each do |myth, info|
|
306
|
+
info[0].each do |file|
|
307
|
+
MythTV[file] = myth
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
def grab_channel(chan_id)
|
312
|
+
STDERR.puts "Grabber must implement grab_channel(chan_id)"
|
313
|
+
exit
|
314
|
+
end
|
315
|
+
def transform(chan_id)
|
316
|
+
STDERR.puts "Grabber must implement transform(chan_id)"
|
317
|
+
exit
|
318
|
+
end
|
319
|
+
|
320
|
+
def initialize
|
321
|
+
@hits = 0
|
322
|
+
# @myname = File.basename($PROGRAM_NAME).gsub(/\..*/, '')
|
323
|
+
@myname = self.class.to_s[7..-8].downcase
|
324
|
+
@chnbasedir = "#{XmltvOptions.basedir}/#{myname}"
|
325
|
+
@config_file_name = XmltvOptions.config || "#{chnbasedir}/config"
|
326
|
+
@reject_file_name = "#{chnbasedir}/rejects"
|
327
|
+
@config = load_config_file
|
328
|
+
@lang = 'nl'
|
329
|
+
@generator = "#{myname}.#{lang}"
|
330
|
+
@base_url = "http://www.#{generator}"
|
331
|
+
@spooldir = config['spooldir'] || "#{chnbasedir}/spool"
|
332
|
+
[ chnbasedir, spooldir ].each do |dir|
|
333
|
+
FileUtils.mkdir_p(dir) unless test(?d, dir)
|
334
|
+
end
|
335
|
+
@channel_list = "#{chnbasedir}/channel_list"
|
336
|
+
@all_channels = load_channel_file(channel_list)
|
337
|
+
end
|
338
|
+
def version
|
339
|
+
"XMLTV::Grabber version 0.8"
|
340
|
+
end
|
341
|
+
|
342
|
+
def outputfile(chan_id)
|
343
|
+
"#{spooldir}/#{channel_name(chan_id)}.xml"
|
344
|
+
end
|
345
|
+
def channel_name(chan_id)
|
346
|
+
"#{chan_id}.#{myname}.#{lang}"
|
347
|
+
end
|
348
|
+
def channel_display(chan_id)
|
349
|
+
all_channels[chan_id]
|
350
|
+
end
|
351
|
+
def cachefile(chan_id)
|
352
|
+
"#{chnbasedir}/#{channel_name(chan_id)}.yaml"
|
353
|
+
end
|
354
|
+
def load_cachefile(chan_id)
|
355
|
+
begin
|
356
|
+
YAML.load_file(cachefile(chan_id))
|
357
|
+
rescue Errno::ENOENT
|
358
|
+
Hash.new
|
359
|
+
end
|
360
|
+
end
|
361
|
+
def check_argv(args)
|
362
|
+
case args[0]
|
363
|
+
when 'all'
|
364
|
+
all_channels.keys
|
365
|
+
when 'new'
|
366
|
+
XmltvOptions.only_new = true
|
367
|
+
all_channels.keys
|
368
|
+
else
|
369
|
+
args
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
def get_channels
|
374
|
+
clean_cache_dir
|
375
|
+
if ! ARGV.empty?
|
376
|
+
ARGV.replace(check_argv(ARGV))
|
377
|
+
else
|
378
|
+
config_channels
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
def load_config_file
|
383
|
+
begin
|
384
|
+
YAML.load_file(config_file_name)
|
385
|
+
rescue
|
386
|
+
h = Hash.new
|
387
|
+
h['channels'] = []
|
388
|
+
h
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
def load_channel_file(fn)
|
393
|
+
if (fn)
|
394
|
+
begin
|
395
|
+
YAML.load_file(fn)
|
396
|
+
rescue Errno::ENOENT
|
397
|
+
fetch_all_channels
|
398
|
+
end
|
399
|
+
end
|
400
|
+
end
|
401
|
+
def save_config
|
402
|
+
loop do
|
403
|
+
if test(?f, config_file_name)
|
404
|
+
File.open(config_file_name, 'w') { |h| h.puts config.to_yaml }
|
405
|
+
return
|
406
|
+
end
|
407
|
+
dir = File.dirname(config_file_name)
|
408
|
+
if test(?d, dir)
|
409
|
+
FileUtils.touch config_file_name
|
410
|
+
elsif test(?e, dir)
|
411
|
+
raise "#{dir} exists, but isn't a directory"
|
412
|
+
else
|
413
|
+
FileUtils.mkdir_p dir
|
414
|
+
end
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
|
419
|
+
def config_channels
|
420
|
+
rsl = config['channels']
|
421
|
+
if rsl.empty?
|
422
|
+
all_channels.keys
|
423
|
+
else
|
424
|
+
rsl
|
425
|
+
end
|
426
|
+
end
|
427
|
+
def clean_spool_dir
|
428
|
+
xmlfiles = config_channels.map{|x| outputfile(x) }
|
429
|
+
Dir["#{spooldir}/*.xml"].each do |fn|
|
430
|
+
unless xmlfiles.include? fn
|
431
|
+
STDERR.puts("removed #{fn} -- not configured")
|
432
|
+
File.unlink fn
|
433
|
+
end
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
|
438
|
+
def clean_cache_dir
|
439
|
+
clean_spool_dir
|
440
|
+
last_time = config['cleaned']
|
441
|
+
if last_time.is_a?(Date) && last_time >= Vandaag
|
442
|
+
return
|
443
|
+
end
|
444
|
+
count = 0
|
445
|
+
channels = config_channels.map{|x| cachefile(x) }
|
446
|
+
Dir["#{chnbasedir}/*.yaml"].each do |fn|
|
447
|
+
unless channels.include? fn
|
448
|
+
STDERR.puts("removed #{fn} -- not configured")
|
449
|
+
File.unlink fn
|
450
|
+
next
|
451
|
+
end
|
452
|
+
cache = YAML.load_file(fn)
|
453
|
+
dels = clean_cache(cache)
|
454
|
+
File.open(fn, 'w') {|h| h.puts cache.to_yaml} if dels > 0
|
455
|
+
count += dels
|
456
|
+
config['cleaned'] = Vandaag
|
457
|
+
end
|
458
|
+
STDERR.puts("#{myname}: Removed #{count} entries") if count > 0
|
459
|
+
|
460
|
+
end
|
461
|
+
|
462
|
+
|
463
|
+
|
464
|
+
|
465
|
+
def proghash(entry, chan_id)
|
466
|
+
progdata = Progdata.new
|
467
|
+
entry.keys.each do |k|
|
468
|
+
dtd = XmlWriter::Programme_dtd.assoc(k)
|
469
|
+
if dtd && dtd.size == 1
|
470
|
+
progdata[k] = entry[k]
|
471
|
+
end
|
472
|
+
end
|
473
|
+
cnm = channel_name(chan_id)
|
474
|
+
progdata['channel'] = MythTV[cnm] || cnm
|
475
|
+
progdata
|
476
|
+
end
|
477
|
+
|
478
|
+
def reject(*args)
|
479
|
+
File.open(reject_file_name, 'a') do |h|
|
480
|
+
h.puts Time.now
|
481
|
+
args.each do |arg|
|
482
|
+
case arg
|
483
|
+
when String, Time, Integer
|
484
|
+
h.puts arg
|
485
|
+
else
|
486
|
+
PP.pp(arg, h)
|
487
|
+
end
|
488
|
+
end
|
489
|
+
h.puts '===='
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
|
494
|
+
|
495
|
+
|
496
|
+
def fix_times(pda)
|
497
|
+
errors = 0
|
498
|
+
startwith = pda.size
|
499
|
+
pda.delete_if do |x|
|
500
|
+
rsl = x.has_key?('stop') && x['stop'] <= x['start']
|
501
|
+
reject('invalid', x) if rsl
|
502
|
+
rsl
|
503
|
+
end
|
504
|
+
starttimes = pda.sort do |x, y|
|
505
|
+
if (a = x['start']) != (b = y['start'])
|
506
|
+
a <=> b
|
507
|
+
else
|
508
|
+
a = x.has_key?('stop') ? x['stop'] : x['start']
|
509
|
+
b = y.has_key?('stop') ? y['stop'] : y['start']
|
510
|
+
b <=> a
|
511
|
+
end
|
512
|
+
end
|
513
|
+
starttimes.each_with_index do |entry, idx|
|
514
|
+
# dump(entry, '++++++++++++++') if entry.has_key?('title') && entry['title'] == 'NOS Studio Voetbal'
|
515
|
+
nxt = starttimes[idx + 1]
|
516
|
+
next unless nxt
|
517
|
+
unless entry.has_key?('stop') && entry['stop'] <= nxt['start']
|
518
|
+
errors += 1
|
519
|
+
entry['stop'] = nxt['start'].dup
|
520
|
+
end
|
521
|
+
end
|
522
|
+
unless starttimes[-1].has_key?('stop')
|
523
|
+
starttimes[-1]['start'] = nil
|
524
|
+
end
|
525
|
+
pda.delete_if do |x|
|
526
|
+
rsl = x['start'].nil? || x['stop'] <= x['start']
|
527
|
+
reject('removed', x) if rsl
|
528
|
+
rsl
|
529
|
+
end
|
530
|
+
@rejects = startwith - pda.size
|
531
|
+
errors
|
532
|
+
end
|
533
|
+
def fetch(url)
|
534
|
+
@hits += 1
|
535
|
+
tries = 0
|
536
|
+
begin
|
537
|
+
open(url) { |h| Hpricot(h) }
|
538
|
+
rescue Errno::ECONNREFUSED, ::Timeout::Error
|
539
|
+
tries += 1
|
540
|
+
sleep 2
|
541
|
+
retry if tries <= 3
|
542
|
+
raise BadSiteError.new
|
543
|
+
end
|
544
|
+
end
|
545
|
+
def save_object(obj, filename)
|
546
|
+
File.open(filename, 'w') do |h|
|
547
|
+
h.puts obj.to_yaml
|
548
|
+
end
|
549
|
+
end
|
550
|
+
def save(*args)
|
551
|
+
file = "/tmp/xmltv_exception.#{$$}"
|
552
|
+
args.each_with_index do |el, i|
|
553
|
+
File.open("#{file}-#{i}", 'w') do |h|
|
554
|
+
h.puts el
|
555
|
+
end
|
556
|
+
end
|
557
|
+
STDERR.puts "See #{file}-*"
|
558
|
+
end
|
559
|
+
|
560
|
+
def dump(*args)
|
561
|
+
args.each do |arg|
|
562
|
+
PP.pp(arg,STDERR)
|
563
|
+
end
|
564
|
+
end
|
565
|
+
def date_stats(chan, time)
|
566
|
+
@days_av[chan].add(time.to_date)
|
567
|
+
end
|
568
|
+
def check_channel(chan_id)
|
569
|
+
all_channels.has_key?(chan_id)
|
570
|
+
end
|
571
|
+
|
572
|
+
def add_channels_to_config
|
573
|
+
XmltvOptions.add.each do |item|
|
574
|
+
if (f = all_channels.has_key?(item)) && ! config['channels'].include?(item)
|
575
|
+
config['channels'] << item
|
576
|
+
STDERR.puts("added #{item} #{channel_display(item)}") if XmltvOptions.verbose
|
577
|
+
else
|
578
|
+
reason = f ? "already included" : "not in channel list"
|
579
|
+
STDERR.puts "#{item} #{reason}"
|
580
|
+
end
|
581
|
+
end
|
582
|
+
save_config
|
583
|
+
end
|
584
|
+
|
585
|
+
def delete_channels_from_config
|
586
|
+
XmltvOptions.del.each do |item|
|
587
|
+
if config['channels'].include?(item)
|
588
|
+
config['channels'].delete(item)
|
589
|
+
STDERR.puts("deleted #{item} #{channel_display(item)}") if XmltvOptions.verbose
|
590
|
+
else
|
591
|
+
STDERR.puts "#{item} not in config list"
|
592
|
+
end
|
593
|
+
end
|
594
|
+
save_config
|
595
|
+
end
|
596
|
+
def printline(it)
|
597
|
+
printf("%-5s %s\n", it, channel_display(it))
|
598
|
+
end
|
599
|
+
def do_list(array)
|
600
|
+
array.sort_by{|x| channel_display(x)}.each do |item|
|
601
|
+
printline item
|
602
|
+
end
|
603
|
+
end
|
604
|
+
def list_config
|
605
|
+
if config['channels'].empty?
|
606
|
+
explan = channel_list ? 'yet' : 'needed'
|
607
|
+
puts "No configuration #{explan} for #{myname}"
|
608
|
+
else
|
609
|
+
puts "Configured channels for #{myname}"
|
610
|
+
do_list(config['channels'])
|
611
|
+
end
|
612
|
+
end
|
613
|
+
def list_all
|
614
|
+
puts "Available channels for #{myname}"
|
615
|
+
do_list(all_channels.keys)
|
616
|
+
end
|
617
|
+
def do_options
|
618
|
+
if XmltvOptions.list
|
619
|
+
list_config
|
620
|
+
elsif XmltvOptions.available
|
621
|
+
list_all
|
622
|
+
else
|
623
|
+
if XmltvOptions.add
|
624
|
+
add_channels_to_config
|
625
|
+
end
|
626
|
+
if XmltvOptions.del
|
627
|
+
delete_channels_from_config
|
628
|
+
end
|
629
|
+
list_config
|
630
|
+
end
|
631
|
+
exit
|
632
|
+
end
|
633
|
+
|
634
|
+
def report(channel, nprogs, errors)
|
635
|
+
STDERR.printf(" %s %-6.6s %-20.20s %5d %5d %5d %5d %8d %5d\n",
|
636
|
+
Time.now.strftime("%H:%M:%S"),
|
637
|
+
channel,
|
638
|
+
channel_display(channel),
|
639
|
+
nprogs,
|
640
|
+
@hits,
|
641
|
+
@days_av[channel].size,
|
642
|
+
errors,
|
643
|
+
test(?s, outputfile(channel)),
|
644
|
+
@rejects)
|
645
|
+
|
646
|
+
@hits = 0
|
647
|
+
end
|
648
|
+
|
649
|
+
def run
|
650
|
+
if XmltvOptions.version
|
651
|
+
puts version
|
652
|
+
exit
|
653
|
+
end
|
654
|
+
do_options if XmltvOptions.action
|
655
|
+
if channel_list && ! test(?f, config_file_name) # We don't want to grab _all_ channels, surely ?
|
656
|
+
list_all
|
657
|
+
puts "\n Do #{$PROGRAM_NAME} --add chn1,chn2,chn3... to create a configuration file"
|
658
|
+
exit
|
659
|
+
end
|
660
|
+
ARGV.delete_if do |arg|
|
661
|
+
begin
|
662
|
+
send(arg)
|
663
|
+
true
|
664
|
+
rescue NoMethodError
|
665
|
+
false
|
666
|
+
end
|
667
|
+
end
|
668
|
+
trap('INT') do
|
669
|
+
STDERR.puts "\nInterrupted\n"
|
670
|
+
raise ArgumentError.new('interrupt')
|
671
|
+
# exit!
|
672
|
+
end
|
673
|
+
|
674
|
+
begin
|
675
|
+
channels = get_channels
|
676
|
+
STDERR.printf("\n%-40s prgs hts days errs bytes rej\n", "#{Date.today}: #{myname}")
|
677
|
+
channels.each do |channel|
|
678
|
+
next if XmltvOptions.only_new && test(?f, outputfile(channel))
|
679
|
+
@days_av = Hash.new { |h,k| h[k] = Set.new }
|
680
|
+
@rejects = 0
|
681
|
+
writer = XmlWriter.new(self)
|
682
|
+
unless check_channel(channel)
|
683
|
+
STDERR.puts "No such channel #{channel}"
|
684
|
+
next
|
685
|
+
end
|
686
|
+
begin
|
687
|
+
nprogs = grab_channel(channel)
|
688
|
+
pda = transform(channel)
|
689
|
+
errors = fix_times(pda)
|
690
|
+
rescue BadChannelError
|
691
|
+
STDERR.puts "Zie #{reject_file_name}"
|
692
|
+
next
|
693
|
+
end
|
694
|
+
writer.write_file(pda, channel)
|
695
|
+
report(channel, nprogs, errors)
|
696
|
+
end
|
697
|
+
rescue SystemExit
|
698
|
+
|
699
|
+
rescue ::Exception => exc
|
700
|
+
STDERR.puts exc.class, exc.message, exc.backtrace
|
701
|
+
if XmltvOptions.mailto
|
702
|
+
MailSender.sendmail(XmltvOptions.mailto, "Xmltv exception", [exc.class, exc.message, exc.backtrace].join("\n"))
|
703
|
+
end
|
704
|
+
raise
|
705
|
+
ensure
|
706
|
+
end
|
707
|
+
save_config
|
708
|
+
end
|
709
|
+
|
710
|
+
end
|
711
|
+
def go
|
712
|
+
if XmltvOptions.add || XmltvOptions.del || XmltvOptions.config
|
713
|
+
STDERR.puts "You must specify a site to configure channels"
|
714
|
+
exit
|
715
|
+
end
|
716
|
+
configs = Dir["#{XmltvOptions.basedir}/*"].select{|x| File::Stat.new(x).directory? }.map{|x| File.basename(x)}
|
717
|
+
if configs.empty?
|
718
|
+
STDERR.puts "No configs found in #{XmltvOptions.basedir}"
|
719
|
+
end
|
720
|
+
todo = configs & Sites
|
721
|
+
exit if todo.empty?
|
722
|
+
todo.sort.each do |grabber|
|
723
|
+
# puts grabber
|
724
|
+
begin
|
725
|
+
require "xmltv/sites/#{grabber}"
|
726
|
+
rescue SystemExit => exc
|
727
|
+
raise unless exc.success?
|
728
|
+
end
|
729
|
+
end
|
730
|
+
end
|
731
|
+
|
732
|
+
module_function :go
|
733
|
+
end
|
734
|
+
if __FILE__ == $PROGRAM_NAME
|
735
|
+
XMLTV::go
|
736
|
+
end
|
737
|
+
|