KeeperPat-feedupdater 0.2.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,717 @@
1
+ #--
2
+ # Copyright (c) 2005 Robert Aman
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ require 'feed_updater/version'
25
+
26
+ # :stopdoc:
27
+ # ROFLCOPTERS?
28
+ # This mainly exists because of the approximately 1 billion different
29
+ # ways to deploy this script.
30
+ if !defined?(FileStalker)
31
+ class FileStalker
32
+ def self.hunt(paths)
33
+ for path in paths
34
+ if File.exists?(File.expand_path(path))
35
+ return File.expand_path(path)
36
+ elsif File.exists?(File.expand_path(
37
+ File.dirname(__FILE__) + path))
38
+ return File.expand_path(File.dirname(__FILE__) + path)
39
+ elsif File.exists?(File.expand_path(
40
+ File.dirname(__FILE__) + "/" + path))
41
+ return File.expand_path(File.dirname(__FILE__) + "/" + path)
42
+ end
43
+ end
44
+ return nil
45
+ end
46
+ end
47
+ end
48
+ # :startdoc:
49
+
50
+ $:.unshift(File.dirname(__FILE__))
51
+ $:.unshift(File.dirname(__FILE__) + "/feed_updater/vendor")
52
+
53
+ # Make it so Rubygems can find the executable
54
+ $:.unshift(File.expand_path(File.dirname(__FILE__) + "/../bin"))
55
+
56
+ require 'rubygems'
57
+
58
+ if !defined?(Daemons)
59
+ begin
60
+ gem('daemons', '>= 0.4.4')
61
+ require('daemons')
62
+ rescue LoadError
63
+ require 'daemons'
64
+ end
65
+ end
66
+
67
+ if !defined?(FeedTools::FEED_TOOLS_VERSION)
68
+ begin
69
+ if File.exists?(File.expand_path(
70
+ File.dirname(__FILE__) + "/../../feedtools/lib"))
71
+ $:.unshift(File.expand_path(
72
+ File.dirname(__FILE__) + "/../../feedtools/lib"))
73
+ require("feed_tools")
74
+ else
75
+ gem('feedtools', '>= 0.2.23')
76
+ require('feedtools')
77
+ end
78
+ rescue LoadError
79
+ require 'feed_tools'
80
+ end
81
+ end
82
+
83
+ require 'benchmark'
84
+ require 'thread'
85
+ require 'logger'
86
+
87
+ class FeedUpdaterLogger < Logger
88
+ attr_accessor :prefix
89
+
90
+ alias_method :old_log, :log
91
+ def log(level, message)
92
+ if defined?(@prefix) && @prefix != nil
93
+ self.old_log(level, "#{self.prefix}#{message}")
94
+ else
95
+ self.old_log(level, message)
96
+ end
97
+ end
98
+
99
+ def debug(message)
100
+ self.log(0, message)
101
+ end
102
+
103
+ def info(message)
104
+ self.log(1, message)
105
+ end
106
+
107
+ def warn(message)
108
+ self.log(2, message)
109
+ end
110
+
111
+ def error(message)
112
+ self.log(3, message)
113
+ end
114
+
115
+ def fatal(message)
116
+ self.log(4, message)
117
+ end
118
+ end
119
+
120
+ module FeedTools
121
+ # A simple daemon for scheduled updating of feeds.
122
+ class FeedUpdater
123
+ # Declares an on_begin event. The given block will be called before the
124
+ # update sequence runs to allow for any setup required. The block is
125
+ # not passed any arguments.
126
+ def self.on_begin(&block)
127
+ raise "No block supplied for on_begin." if block.nil?
128
+ @@on_begin = block
129
+ end
130
+
131
+ # Declares an on_update event. The given block will be called after
132
+ # every feed update. The block is passed the feed object that was loaded
133
+ # and the time it took in seconds to successfully load it.
134
+ def self.on_update(&block)
135
+ raise "No block supplied for on_update." if block.nil?
136
+ @@on_update = block
137
+ end
138
+
139
+ # Declares an on_error event. The given block will be called after
140
+ # an error occurs during a feed update. The block is passed the href of
141
+ # the feed that errored out, and the exception object that was raised.
142
+ def self.on_error(&block)
143
+ raise "No block supplied for on_error." if block.nil?
144
+ @@on_error = block
145
+ end
146
+
147
+ # Declares an on_complete event. The given block will be called after
148
+ # all feeds have been updated. The block is passed a list of feeds that
149
+ # FeedUpdater attempted to update.
150
+ def self.on_complete(&block)
151
+ raise "No block supplied for on_complete." if block.nil?
152
+ @@on_complete = block
153
+ end
154
+
155
+ # Returns the initial directory that the daemon was started from.
156
+ def initial_directory()
157
+ @initial_directory = nil if !defined?(@initial_directory)
158
+ return @initial_directory
159
+ end
160
+
161
+ # Returns the directory where the pid files are stored.
162
+ def pid_file_dir()
163
+ @pid_file_dir = nil if !defined?(@pid_file_dir)
164
+ return @pid_file_dir
165
+ end
166
+
167
+ # Sets the directory where the pid files are stored.
168
+ def pid_file_dir=(new_pid_file_dir)
169
+ @pid_file_dir = new_pid_file_dir
170
+ end
171
+
172
+ # Returns the directory where the log files are stored.
173
+ def log_file_dir()
174
+ @log_file_dir = nil if !defined?(@log_file_dir)
175
+ return @log_file_dir
176
+ end
177
+
178
+ # Sets the directory where the log files are stored.
179
+ def log_file_dir=(new_log_file_dir)
180
+ @log_file_dir = new_log_file_dir
181
+ end
182
+
183
+ # Returns the path to the log file.
184
+ def log_file()
185
+ if !defined?(@log_file) || @log_file.nil?
186
+ if self.log_file_dir.nil?
187
+ @log_file = File.expand_path("./feed_updater.log")
188
+ else
189
+ @log_file = File.expand_path(
190
+ self.log_file_dir + "/feed_updater.log")
191
+ end
192
+ end
193
+ return @log_file
194
+ end
195
+
196
+ # Returns the logger object.
197
+ def logger()
198
+ if !defined?(@logger) || @logger.nil?
199
+ @logger = FeedUpdaterLogger.new(self.log_file)
200
+ @logger.level = self.updater_options[:log_level]
201
+ @logger.datetime_format = nil
202
+ @logger.progname = nil
203
+ @logger.prefix = "FeedUpdater".ljust(20)
204
+ end
205
+ return @logger
206
+ end
207
+
208
+ # Restarts the logger object. This needs to be done after the program
209
+ # forks.
210
+ def restart_logger()
211
+ begin
212
+ self.logger.close()
213
+ rescue IOError
214
+ end
215
+ @logger = FeedUpdaterLogger.new(self.log_file)
216
+ @logger.level = self.updater_options[:log_level]
217
+ @logger.datetime_format = nil
218
+ @logger.progname = nil
219
+ @logger.prefix = "FeedUpdater".ljust(20)
220
+ end
221
+
222
+ # Returns a list of feeds to be updated.
223
+ def feed_href_list()
224
+ if !defined?(@feed_href_list) || @feed_href_list.nil?
225
+ @feed_href_list_override = false
226
+ if self.updater_options.nil?
227
+ @feed_href_list = nil
228
+ elsif self.updater_options[:feed_href_list].kind_of?(Array)
229
+ @feed_href_list = self.updater_options[:feed_href_list]
230
+ else
231
+ @feed_href_list = nil
232
+ end
233
+ end
234
+ return @feed_href_list
235
+ end
236
+
237
+ # Sets a list of feeds to be updated.
238
+ def feed_href_list=(new_feed_href_list)
239
+ @feed_href_list_override = true
240
+ @feed_href_list = new_feed_href_list
241
+ end
242
+
243
+ # Returns either :running or :stopped depending on the daemon's current
244
+ # status.
245
+ def status()
246
+ @status = :stopped if @status.nil?
247
+ return @status
248
+ end
249
+
250
+ # Returns a hash of the currently set updater options.
251
+ def updater_options()
252
+ if !defined?(@updater_options) || @updater_options.nil?
253
+ @updater_options = {
254
+ :start_delay => true,
255
+ :threads => 1,
256
+ :sleep_time => 60,
257
+ :log_level => 0
258
+ }
259
+ end
260
+ return @updater_options
261
+ end
262
+
263
+ # Returns a hash of the currently set daemon options.
264
+ def daemon_options()
265
+ if !defined?(@daemon_options) || @daemon_options.nil?
266
+ @daemon_options = {
267
+ :app_name => "feed_updater_daemon",
268
+ :dir_mode => :normal,
269
+ :dir => self.pid_file_dir,
270
+ :backtrace => true,
271
+ :ontop => false
272
+ }
273
+ end
274
+ @daemon_options[:dir] = self.pid_file_dir
275
+ return @daemon_options
276
+ end
277
+
278
+ # Returns a reference to the daemon application.
279
+ def application()
280
+ @application = nil if !defined?(@application)
281
+ return @application
282
+ end
283
+
284
+ # Returns the process id of the daemon. This should return nil if the
285
+ # daemon is not running.
286
+ def pid()
287
+ if File.exists?(File.expand_path(pid_file_dir + "/feed_updater.pid"))
288
+ begin
289
+ pid_file = File.open(
290
+ File.expand_path(pid_file_dir + "/feed_updater.pid"), "r")
291
+ return pid_file.read.to_s.strip.to_i
292
+ rescue Exception
293
+ return nil
294
+ end
295
+ else
296
+ return nil if self.application.nil?
297
+ begin
298
+ return self.application.pid.pid
299
+ rescue Exception
300
+ return nil
301
+ end
302
+ end
303
+ end
304
+
305
+ def cloaker(&blk) #:nodoc:
306
+ (class << self; self; end).class_eval do
307
+ define_method(:cloaker_, &blk)
308
+ meth = instance_method(:cloaker_)
309
+ remove_method(:cloaker_)
310
+ meth
311
+ end
312
+ end
313
+ protected :cloaker
314
+
315
+ # Starts the daemon.
316
+ def start()
317
+ self.logger.prefix = "FeedUpdater".ljust(20)
318
+ if !defined?(@@on_update) || @@on_update.nil?
319
+ raise "No on_update handler block given."
320
+ end
321
+ if self.status == :running || self.pid != nil
322
+ puts "FeedUpdater is already running."
323
+ return self.pid
324
+ end
325
+ if defined?(ActiveRecord)
326
+ ActiveRecord::Base.allow_concurrency = true
327
+ end
328
+
329
+ @initial_directory = File.expand_path(".")
330
+
331
+ if @application.nil?
332
+ self.logger()
333
+ feed_update_proc = lambda do
334
+ # Reestablish correct location
335
+ Dir.chdir(File.expand_path(self.initial_directory))
336
+
337
+ sleep(1)
338
+
339
+ self.restart_logger()
340
+ self.logger.info("Using environment: #{FEED_TOOLS_ENV}")
341
+
342
+ if FeedTools.configurations[:feed_cache].nil?
343
+ FeedTools.configurations[:feed_cache] =
344
+ "FeedTools::DatabaseFeedCache"
345
+ end
346
+
347
+ if !FeedTools.feed_cache.connected?
348
+ FeedTools.configurations[:feed_cache] =
349
+ "FeedTools::DatabaseFeedCache"
350
+ FeedTools.feed_cache.initialize_cache()
351
+ begin
352
+ ActiveRecord::Base.default_timezone = :utc
353
+ ActiveRecord::Base.connection
354
+ rescue Exception
355
+ config_path = FileStalker.hunt([
356
+ "./config/database.yml",
357
+ "../config/database.yml",
358
+ "../../config/database.yml",
359
+ "./database.yml",
360
+ "../database.yml",
361
+ "../../database.yml"
362
+ ])
363
+ if config_path != nil
364
+ require 'yaml'
365
+ config_hash = File.open(config_path) do |file|
366
+ config_hash = YAML.load(file.read)
367
+ unless config_hash[FEED_TOOLS_ENV].nil?
368
+ config_hash = config_hash[FEED_TOOLS_ENV]
369
+ end
370
+ config_hash
371
+ end
372
+ self.logger.info(
373
+ "Attempting to manually load the database " +
374
+ "connection described in:")
375
+ self.logger.info(config_path)
376
+ begin
377
+ ActiveRecord::Base.configurations = config_hash
378
+ ActiveRecord::Base.establish_connection(config_hash)
379
+ ActiveRecord::Base.connection
380
+ rescue Exception => error
381
+ self.logger.fatal(
382
+ "Error initializing database:")
383
+ self.logger.fatal(error)
384
+ exit
385
+ end
386
+ else
387
+ self.logger.fatal(
388
+ "The database.yml file does not appear " +
389
+ "to exist.")
390
+ exit
391
+ end
392
+ end
393
+ end
394
+ if !FeedTools.feed_cache.connected?
395
+ self.logger.fatal("Not connected to the feed cache.")
396
+ if FeedTools.feed_cache.respond_to?(:table_exists?)
397
+ if !FeedTools.feed_cache.table_exists?
398
+ self.logger.fatal(
399
+ "The FeedTools cache table is missing.")
400
+ end
401
+ end
402
+ exit
403
+ end
404
+
405
+ # A random start delay is introduced so that we don't have multiple
406
+ # feed updater daemons getting kicked off at the same time by
407
+ # multiple users.
408
+ if self.updater_options[:start_delay]
409
+ delay = (rand(45) + 15)
410
+ self.logger.info("Startup delay set for #{delay} minutes.")
411
+ sleep(delay.minutes)
412
+ end
413
+
414
+ # The main feed update loop.
415
+ loop do
416
+ result = nil
417
+ sleepy_time = self.updater_options[:sleep_time].to_i.minutes
418
+ begin
419
+ result = Benchmark.measure do
420
+ self.update_feeds()
421
+ end
422
+ self.logger.info(
423
+ "#{@feed_href_list.size} feed(s) updated " +
424
+ "in #{result.real.round} seconds.")
425
+ sleepy_time =
426
+ (self.updater_options[:sleep_time].to_i.minutes -
427
+ result.real.round)
428
+ rescue Exception => error
429
+ self.logger.error("Feed update sequence errored out.")
430
+ self.logger.error(error.class.name + ": " + error.message)
431
+ self.logger.error("\n" + error.backtrace.join("\n").to_s)
432
+ end
433
+
434
+ @feed_href_list = nil
435
+ @feed_href_list_override = false
436
+ ObjectSpace.garbage_collect()
437
+ if sleepy_time > 0
438
+ self.logger.info(
439
+ "Sleeping for #{(sleepy_time / 1.minute.to_f).round} " +
440
+ "minutes...")
441
+ sleep(sleepy_time)
442
+ else
443
+ self.logger.info(
444
+ "Update took more than " +
445
+ "#{self.updater_options[:sleep_time].to_i} minutes, " +
446
+ "restarting immediately.")
447
+ end
448
+ end
449
+ end
450
+ options = self.daemon_options.merge({
451
+ :proc => feed_update_proc,
452
+ :mode => :proc
453
+ })
454
+ @application = Daemons::ApplicationGroup.new(
455
+ 'feed_updater', options).new_application(options)
456
+ @application.start()
457
+ else
458
+ @application.start()
459
+ end
460
+ self.logger.prefix = nil
461
+ self.logger.level = 0
462
+ self.logger.info("-" * 79)
463
+ self.logger.info("Daemon started, " +
464
+ "FeedUpdater #{FeedTools::FEED_UPDATER_VERSION::STRING} / " +
465
+ "FeedTools #{FeedTools::FEED_TOOLS_VERSION::STRING}")
466
+ self.logger.info(" @ #{Time.now.utc.to_s}")
467
+ self.logger.info("-" * 79)
468
+ self.logger.level = self.updater_options[:log_level]
469
+ self.logger.prefix = "FeedUpdater".ljust(20)
470
+ @status = :running
471
+ return self.pid
472
+ end
473
+
474
+ # Stops the daemon.
475
+ def stop()
476
+ if self.pid.nil?
477
+ puts "FeedUpdater isn't running."
478
+ end
479
+ begin
480
+ # Die.
481
+ Process.kill('TERM', self.pid)
482
+ rescue Exception
483
+ end
484
+ begin
485
+ # No, really, I mean it. You need to die.
486
+ system("kill #{self.pid} 2> /dev/null")
487
+
488
+ # Perhaps I wasn't clear somehow?
489
+ system("kill -9 #{self.pid} 2> /dev/null")
490
+ rescue Exception
491
+ end
492
+ begin
493
+ # Clean the pid file up.
494
+ if File.exists?(
495
+ File.expand_path(self.pid_file_dir + "/feed_updater.pid"))
496
+ File.delete(
497
+ File.expand_path(self.pid_file_dir + "/feed_updater.pid"))
498
+ end
499
+ rescue Exception
500
+ end
501
+ self.logger.prefix = "FeedUpdater".ljust(20)
502
+ self.logger.level = 0
503
+ self.logger.info("Daemon stopped.")
504
+ self.logger.level = self.updater_options[:log_level]
505
+ @status = :stopped
506
+ return nil if self.application.nil?
507
+ begin
508
+ self.application.stop()
509
+ rescue Exception
510
+ end
511
+ return nil
512
+ end
513
+
514
+ # Restarts the daemon.
515
+ def restart()
516
+ self.stop()
517
+ self.start()
518
+ end
519
+
520
+ def progress_precentage()
521
+ if !defined?(@remaining_href_list) || !defined?(@feed_href_list)
522
+ return nil
523
+ end
524
+ if @remaining_href_list.nil? || @feed_href_list.nil?
525
+ return nil
526
+ end
527
+ if @feed_href_list == []
528
+ return nil
529
+ end
530
+ return 100.0 - (100.0 *
531
+ (@remaining_href_list.size.to_f / @feed_href_list.size.to_f))
532
+ end
533
+
534
+ # Updates all of the feeds.
535
+ def update_feeds()
536
+ self.logger.level = 0
537
+ self.logger.prefix = "FeedUpdater".ljust(20)
538
+ ObjectSpace.garbage_collect()
539
+ if defined?(@@on_begin) && @@on_begin != nil
540
+ self.logger.info("Running custom startup event...")
541
+ self.cloaker(&(@@on_begin)).bind(self).call()
542
+ end
543
+ if defined?(@feed_href_list_override) && @feed_href_list_override
544
+ self.logger.info("Using custom feed list...")
545
+ self.feed_href_list()
546
+ else
547
+ self.logger.info("Loading default feed list...")
548
+ begin
549
+ expire_time = (Time.now - 1.hour).utc
550
+ expire_time_string = sprintf('%04d-%02d-%02d %02d:%02d:%02d',
551
+ expire_time.year, expire_time.month, expire_time.day,
552
+ expire_time.hour, expire_time.min, expire_time.sec)
553
+ @feed_href_list =
554
+ FeedTools.feed_cache.connection.execute(
555
+ "SELECT href FROM cached_feeds WHERE " +
556
+ "last_retrieved < '#{expire_time_string}'").to_a.flatten
557
+ rescue Exception
558
+ self.logger.warn("Default feed list failed, using fallback.")
559
+ @feed_href_list =
560
+ FeedTools.feed_cache.find(:all).collect do |feed|
561
+ feed.href
562
+ end
563
+ self.logger.warn(
564
+ "Fallback succeeded. Custom feed list override recommended.")
565
+ end
566
+ end
567
+ self.logger.info("Updating #{@feed_href_list.size} feed(s)...")
568
+ self.logger.level = self.updater_options[:log_level]
569
+
570
+ @threads = []
571
+ @remaining_href_list = @feed_href_list.dup
572
+
573
+ ObjectSpace.garbage_collect()
574
+
575
+ begin_updating = false
576
+ self.logger.info(
577
+ "Starting up #{self.updater_options[:threads]} thread(s)...")
578
+
579
+ mutex = Mutex.new
580
+ for i in 0...self.updater_options[:threads]
581
+ updater_thread = Thread.new do
582
+ self.logger.level = self.updater_options[:log_level]
583
+ self.logger.datetime_format = "%s"
584
+ self.logger.progname = "FeedUpdater".ljust(20)
585
+
586
+ while !Thread.current.respond_to?(:thread_id) &&
587
+ begin_updating == false
588
+ Thread.pass
589
+ end
590
+ mutex.synchronize do
591
+ self.logger.prefix =
592
+ "Thread #{Thread.current.thread_id} ".ljust(20)
593
+ self.logger.info("Thread started.")
594
+
595
+ begin
596
+ FeedTools.feed_cache.initialize_cache()
597
+ if !FeedTools.feed_cache.set_up_correctly?
598
+ self.logger.info("Problem with cache connection...")
599
+ end
600
+ rescue Exception => error
601
+ self.logger.info(error)
602
+ end
603
+ end
604
+
605
+ ObjectSpace.garbage_collect()
606
+ Thread.pass
607
+
608
+ while @remaining_href_list.size > 0
609
+ progress = nil
610
+ mutex.synchronize do
611
+ Thread.current.progress = self.progress_precentage()
612
+ Thread.current.href = @remaining_href_list.shift()
613
+ progress = sprintf("%.2f", Thread.current.progress)
614
+ end
615
+ begin
616
+ begin
617
+ Thread.current.feed = nil
618
+ feed_load_benchmark = Benchmark.measure do
619
+ Thread.current.feed =
620
+ FeedTools::Feed.open(Thread.current.href)
621
+ end
622
+ Thread.pass
623
+ if Thread.current.feed.live?
624
+ unless @@on_update.nil?
625
+ mutex.synchronize do
626
+ progress = sprintf("%.2f", Thread.current.progress)
627
+ self.logger.prefix =
628
+ ("Thread #{Thread.current.thread_id} (#{progress}%)"
629
+ ).ljust(20)
630
+ self.cloaker(&(@@on_update)).bind(self).call(
631
+ Thread.current.feed, feed_load_benchmark.real)
632
+ end
633
+ end
634
+ else
635
+ mutex.synchronize do
636
+ progress = sprintf("%.2f", Thread.current.progress)
637
+ self.logger.prefix =
638
+ ("Thread #{Thread.current.thread_id} (#{progress}%)"
639
+ ).ljust(20)
640
+ self.logger.info(
641
+ "'#{Thread.current.href}' unchanged " +
642
+ "or unavailable, skipping.")
643
+ end
644
+ end
645
+ rescue Exception => error
646
+ mutex.synchronize do
647
+ progress = sprintf("%.2f", Thread.current.progress)
648
+ self.logger.prefix =
649
+ ("Thread #{Thread.current.thread_id} (#{progress}%)"
650
+ ).ljust(20)
651
+ if @@on_error != nil
652
+ self.cloaker(&(@@on_error)).bind(self).call(
653
+ Thread.current.href, error)
654
+ else
655
+ self.logger.error(
656
+ "Error updating '#{Thread.current.href}':")
657
+ self.logger.error(error.class.name + ": " + error.message)
658
+ self.logger.error(error.class.backtrace)
659
+ end
660
+ end
661
+ end
662
+ rescue Exception => error
663
+ mutex.synchronize do
664
+ progress = sprintf("%.2f", Thread.current.progress)
665
+ self.logger.prefix = ("Thread #{Thread.current.thread_id} " +
666
+ "(#{progress}%)"
667
+ ).ljust(20)
668
+ self.logger.fatal("Critical unhandled error.")
669
+ self.logger.fatal("Error updating '#{Thread.current.href}':")
670
+ self.logger.fatal(error.class.name + ": " + error.message)
671
+ self.logger.fatal(error.class.backtrace)
672
+ end
673
+ end
674
+ ObjectSpace.garbage_collect()
675
+ Thread.pass
676
+ end
677
+ end
678
+ @threads << updater_thread
679
+ class <<updater_thread
680
+ attr_accessor :thread_id
681
+ attr_accessor :progress
682
+ attr_accessor :href
683
+ attr_accessor :feed
684
+ end
685
+ updater_thread.thread_id = i
686
+ end
687
+ mutex.synchronize do
688
+ self.logger.prefix = "FeedUpdater".ljust(20)
689
+ self.logger.info(
690
+ "#{@threads.size} thread(s) successfully started...")
691
+ begin_updating = true
692
+ end
693
+ Thread.pass
694
+
695
+ ObjectSpace.garbage_collect()
696
+ Thread.pass
697
+ for i in 0...@threads.size
698
+ mutex.synchronize do
699
+ self.logger.prefix = "FeedUpdater".ljust(20)
700
+ self.logger.info(
701
+ "Joining on thread #{@threads[i].thread_id}...")
702
+ end
703
+ @threads[i].join
704
+ end
705
+ self.logger.prefix = "FeedUpdater".ljust(20)
706
+ ObjectSpace.garbage_collect()
707
+
708
+ self.logger.progname = nil
709
+ unless @@on_complete.nil?
710
+ self.cloaker(&(@@on_complete)).bind(self).call(@feed_href_list)
711
+ end
712
+ self.logger.level = 0
713
+ self.logger.info("Finished updating.")
714
+ self.logger.level = self.updater_options[:log_level]
715
+ end
716
+ end
717
+ end