KeeperPat-feedupdater 0.2.6
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/CHANGELOG +21 -0
- data/README +34 -0
- data/bin/feed_updater +317 -0
- data/config/feed_updater.yml +22 -0
- data/example/custom_updater.rb +24 -0
- data/lib/feed_updater/vendor/daemons/application.rb +298 -0
- data/lib/feed_updater/vendor/daemons/application_group.rb +150 -0
- data/lib/feed_updater/vendor/daemons/cmdline.rb +106 -0
- data/lib/feed_updater/vendor/daemons/controller.rb +134 -0
- data/lib/feed_updater/vendor/daemons/daemonize.rb +265 -0
- data/lib/feed_updater/vendor/daemons/exceptions.rb +28 -0
- data/lib/feed_updater/vendor/daemons/monitor.rb +126 -0
- data/lib/feed_updater/vendor/daemons/pid.rb +61 -0
- data/lib/feed_updater/vendor/daemons/pidfile.rb +99 -0
- data/lib/feed_updater/vendor/daemons/pidmem.rb +10 -0
- data/lib/feed_updater/vendor/daemons.rb +274 -0
- data/lib/feed_updater/version.rb +9 -0
- data/lib/feed_updater.rb +717 -0
- data/rakefile +120 -0
- metadata +91 -0
data/lib/feed_updater.rb
ADDED
@@ -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
|