feedupdater 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,401 @@
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
+ $:.unshift(File.dirname(__FILE__))
27
+ $:.unshift(File.dirname(__FILE__) + "/feed_updater/vendor")
28
+
29
+ # Make it so Rubygems can find the executable
30
+ $:.unshift(File.expand_path(File.dirname(__FILE__) + "/../bin"))
31
+
32
+ require 'rubygems'
33
+
34
+ if !defined?(Daemons)
35
+ begin
36
+ require_gem('daemons', '>= 0.4.4')
37
+ rescue LoadError
38
+ require 'daemons'
39
+ end
40
+ end
41
+
42
+ if !defined?(FeedTools::FEED_TOOLS_VERSION)
43
+ begin
44
+ if File.exists?(File.expand_path(
45
+ File.dirname(__FILE__) + "/../../feedtools/lib"))
46
+ $:.unshift(File.expand_path(
47
+ File.dirname(__FILE__) + "/../../feedtools/lib"))
48
+ require("feed_tools")
49
+ else
50
+ require_gem('feedtools', '>= 0.2.23')
51
+ end
52
+ rescue LoadError
53
+ require 'feed_tools'
54
+ end
55
+ end
56
+
57
+ require 'benchmark'
58
+
59
+ module FeedTools
60
+ # A simple daemon for scheduled updating of feeds.
61
+ class FeedUpdater
62
+ # Declares an on_update event. The given block will be called after
63
+ # every feed update. The block is passed the feed object that was loaded
64
+ # and the time it took in seconds to successfully load it.
65
+ def self.on_update(&block)
66
+ raise "No block supplied for on_update." if block.nil?
67
+ @@on_update = block
68
+ end
69
+
70
+ # Declares an on_error event. The given block will be called after
71
+ # an error occurs during a feed update. The block is passed the href of
72
+ # the feed that errored out, and the exception object that was raised.
73
+ def self.on_error(&block)
74
+ raise "No block supplied for on_error." if block.nil?
75
+ @@on_error = block
76
+ end
77
+
78
+ # Declares an on_complete event. The given block will be called after
79
+ # all feeds have been updated. The block is passed a list of feeds that
80
+ # FeedUpdater attempted to update.
81
+ def self.on_complete(&block)
82
+ raise "No block supplied for on_complete." if block.nil?
83
+ @@on_complete = block
84
+ end
85
+
86
+ # Returns the initial directory that the daemon was started from.
87
+ def initial_directory()
88
+ @initial_directory = nil if !defined?(@initial_directory)
89
+ return @initial_directory
90
+ end
91
+
92
+ # Returns the directory where the pid files are stored.
93
+ def pid_file_dir()
94
+ @pid_file_dir = nil if !defined?(@pid_file_dir)
95
+ return @pid_file_dir
96
+ end
97
+
98
+ # Sets the directory where the pid files are stored.
99
+ def pid_file_dir=(new_pid_file_dir)
100
+ @pid_file_dir = new_pid_file_dir
101
+ end
102
+
103
+ # Returns the directory where the log files are stored.
104
+ def log_file_dir()
105
+ @log_file_dir = nil if !defined?(@log_file_dir)
106
+ return @log_file_dir
107
+ end
108
+
109
+ # Sets the directory where the log files are stored.
110
+ def log_file_dir=(new_log_file_dir)
111
+ @log_file_dir = new_log_file_dir
112
+ end
113
+
114
+ # Returns the path to the log file.
115
+ def log_file()
116
+ if !defined?(@log_file) || @log_file.nil?
117
+ if self.log_file_dir.nil?
118
+ @log_file = File.expand_path("./feed_updater.log")
119
+ else
120
+ @log_file = File.expand_path(
121
+ self.log_file_dir + "/feed_updater.log")
122
+ end
123
+ end
124
+ return @log_file
125
+ end
126
+
127
+ # Returns the logger object.
128
+ def logger()
129
+ if !defined?(@logger) || @logger.nil?
130
+ @logger = Logger.new(self.log_file)
131
+ end
132
+ return @logger
133
+ end
134
+
135
+ # Restarts the logger object. This needs to be done after the program
136
+ # forks.
137
+ def restart_logger()
138
+ begin
139
+ self.logger.close()
140
+ rescue IOError
141
+ end
142
+ @logger = Logger.new(self.log_file)
143
+ end
144
+
145
+ # Returns a list of feeds to be updated.
146
+ def feed_href_list()
147
+ if !defined?(@feed_href_list) || @feed_href_list.nil?
148
+ @feed_href_list_override = false
149
+ if self.updater_options.nil?
150
+ @feed_href_list = nil
151
+ elsif self.updater_options[:feed_href_list].kind_of?(Array)
152
+ @feed_href_list = self.updater_options[:feed_href_list]
153
+ else
154
+ @feed_href_list = nil
155
+ end
156
+ end
157
+ return @feed_href_list
158
+ end
159
+
160
+ # Sets a list of feeds to be updated.
161
+ def feed_href_list=(new_feed_href_list)
162
+ @feed_href_list_override = true
163
+ @feed_href_list = new_feed_href_list
164
+ end
165
+
166
+ # Returns either :running or :stopped depending on the daemon's current
167
+ # status.
168
+ def status()
169
+ @status = :stopped if @status.nil?
170
+ return @status
171
+ end
172
+
173
+ # Returns a hash of the currently set updater options.
174
+ def updater_options()
175
+ if !defined?(@updater_options) || @updater_options.nil?
176
+ @updater_options = {
177
+ :start_delay => true
178
+ }
179
+ end
180
+ return @updater_options
181
+ end
182
+
183
+ # Returns a hash of the currently set daemon options.
184
+ def daemon_options()
185
+ if !defined?(@daemon_options) || @daemon_options.nil?
186
+ @daemon_options = {
187
+ :app_name => "feed_updater_daemon",
188
+ :dir_mode => :normal,
189
+ :dir => self.pid_file_dir,
190
+ :backtrace => true,
191
+ :ontop => false
192
+ }
193
+ end
194
+ @daemon_options[:dir] = self.pid_file_dir
195
+ return @daemon_options
196
+ end
197
+
198
+ # Returns a reference to the daemon application.
199
+ def application()
200
+ @application = nil if !defined?(@application)
201
+ return @application
202
+ end
203
+
204
+ # Returns the process id of the daemon. This should return nil if the
205
+ # daemon is not running.
206
+ def pid()
207
+ if File.exists?(File.expand_path(pid_file_dir + "/feed_updater.pid"))
208
+ begin
209
+ pid_file = File.open(
210
+ File.expand_path(pid_file_dir + "/feed_updater.pid"), "r")
211
+ return pid_file.read.to_s.strip.to_i
212
+ rescue Exception
213
+ return nil
214
+ end
215
+ else
216
+ return nil if self.application.nil?
217
+ begin
218
+ return self.application.pid.pid
219
+ rescue Exception
220
+ return nil
221
+ end
222
+ end
223
+ end
224
+
225
+ def cloaker(&blk) #:nodoc:
226
+ (class << self; self; end).class_eval do
227
+ define_method(:cloaker_, &blk)
228
+ meth = instance_method(:cloaker_)
229
+ remove_method(:cloaker_)
230
+ meth
231
+ end
232
+ end
233
+ protected :cloaker
234
+
235
+ # Starts the daemon.
236
+ def start()
237
+ if !defined?(@@on_update) || @@on_update.nil?
238
+ raise "No on_update handler block given."
239
+ end
240
+ if self.status == :running || self.pid != nil
241
+ puts "FeedUpdater is already running."
242
+ return self.pid
243
+ end
244
+
245
+ @initial_directory = File.expand_path(".")
246
+
247
+ if @application.nil?
248
+ self.logger()
249
+ feed_update_proc = lambda do
250
+ # Reestablish correct location
251
+ Dir.chdir(File.expand_path(self.initial_directory))
252
+
253
+ self.restart_logger()
254
+
255
+ if FeedTools.configurations[:feed_cache].nil?
256
+ FeedTools.configurations[:feed_cache] =
257
+ "FeedTools::DatabaseFeedCache"
258
+ end
259
+ if !FeedTools.feed_cache.connected?
260
+ FeedTools.feed_cache.initialize_cache()
261
+ end
262
+ if !FeedTools.feed_cache.connected?
263
+ self.logger.fatal(
264
+ "Not connected to the feed cache. Please create database.yml.")
265
+ exit
266
+ end
267
+
268
+ # A random start delay is introduced so that we don't have multiple
269
+ # feed updater daemons getting kicked off at the same time by
270
+ # multiple users.
271
+ if self.updater_options[:start_delay]
272
+ delay = (rand(45) + 15)
273
+ self.logger.info("Startup delay set for #{delay} minutes.")
274
+ sleep(delay.minutes)
275
+ end
276
+
277
+ # The main feed update loop.
278
+ loop do
279
+ result = Benchmark.measure do
280
+ self.update_feeds()
281
+ end
282
+ self.logger.info(
283
+ "#{@feed_href_list.size} feeds updated " +
284
+ "in #{result.real.round} seconds.")
285
+ ObjectSpace.garbage_collect
286
+ sleepy_time = 1.hour - result.real.round
287
+ if sleepy_time > 0
288
+ self.logger.info(
289
+ "Sleeping for #{(sleepy_time / 60.0).round} minutes...")
290
+ sleep(sleepy_time)
291
+ else
292
+ self.logger.info(
293
+ "Update took more than 60 minutes, restarting immediately.")
294
+ end
295
+ end
296
+ end
297
+ options = self.daemon_options.merge({
298
+ :proc => feed_update_proc,
299
+ :mode => :proc
300
+ })
301
+ @application = Daemons::ApplicationGroup.new(
302
+ 'feed_updater', options).new_application(options)
303
+ @application.start()
304
+ else
305
+ @application.start()
306
+ end
307
+ self.logger.info("-" * 79)
308
+ self.logger.info("Daemon started, " +
309
+ "FeedUpdater #{FeedTools::FEED_UPDATER_VERSION::STRING} / " +
310
+ "FeedTools #{FeedTools::FEED_TOOLS_VERSION::STRING}")
311
+ self.logger.info(" @ #{Time.now.utc.to_s}")
312
+ @status = :running
313
+ return self.pid
314
+ end
315
+
316
+ # Stops the daemon.
317
+ def stop()
318
+ if self.pid.nil?
319
+ puts "FeedUpdater isn't running."
320
+ end
321
+ begin
322
+ # Die.
323
+ Process.kill('TERM', self.pid)
324
+ rescue Exception
325
+ end
326
+ begin
327
+ # No, really, I mean it. You need to die.
328
+ system("kill #{self.pid} 2> /dev/null")
329
+
330
+ # Perhaps I wasn't clear somehow?
331
+ system("kill -9 #{self.pid} 2> /dev/null")
332
+ rescue Exception
333
+ end
334
+ begin
335
+ # Clean the pid file up.
336
+ if File.exists?(
337
+ File.expand_path(self.pid_file_dir + "/feed_updater.pid"))
338
+ File.delete(
339
+ File.expand_path(self.pid_file_dir + "/feed_updater.pid"))
340
+ end
341
+ rescue Exception
342
+ end
343
+ self.logger.info("Daemon stopped.")
344
+ @status = :stopped
345
+ return nil if self.application.nil?
346
+ begin
347
+ self.application.stop()
348
+ rescue Exception
349
+ end
350
+ return nil
351
+ end
352
+
353
+ # Restarts the daemon.
354
+ def restart()
355
+ self.stop()
356
+ self.start()
357
+ end
358
+
359
+ # Updates all of the feeds.
360
+ def update_feeds()
361
+ if defined?(@feed_href_list_override) && @feed_href_list_override
362
+ self.feed_href_list()
363
+ else
364
+ @feed_href_list =
365
+ FeedTools.feed_cache.find(:all).collect do |cache_object|
366
+ cache_object.href
367
+ end
368
+ end
369
+ self.logger.info("Updating #{@feed_href_list.size} feeds...")
370
+ for href in @feed_href_list
371
+ begin
372
+ begin
373
+ feed = nil
374
+ feed_load_benchmark = Benchmark.measure do
375
+ feed = FeedTools::Feed.open(href)
376
+ end
377
+ unless @@on_update.nil?
378
+ self.cloaker(&(@@on_update)).bind(self).call(
379
+ feed, feed_load_benchmark.real)
380
+ end
381
+ rescue Exception => error
382
+ if @@on_error != nil
383
+ self.cloaker(&(@@on_error)).bind(self).call(href, error)
384
+ else
385
+ self.logger.info("Error updating '#{href}':")
386
+ self.logger.info(error)
387
+ end
388
+ end
389
+ rescue Exception => error
390
+ self.logger.info("Critical unhandled error.")
391
+ self.logger.info("Error updating '#{href}':")
392
+ self.logger.info(error)
393
+ end
394
+ end
395
+ unless @@on_complete.nil?
396
+ self.cloaker(&(@@on_complete)).bind(self).call(@feed_href_list)
397
+ end
398
+ self.logger.info("Finished updating.")
399
+ end
400
+ end
401
+ end
data/rakefile ADDED
@@ -0,0 +1,121 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+ require 'rake/gempackagetask'
5
+ require 'rake/contrib/rubyforgepublisher'
6
+
7
+ require 'date'
8
+ require 'rbconfig'
9
+
10
+ require File.join(File.dirname(__FILE__), 'lib/feed_updater', 'version')
11
+
12
+ PKG_NAME = 'feedupdater'
13
+ PKG_VERSION = FeedTools::FEED_UPDATER_VERSION::STRING
14
+ PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
15
+
16
+ RELEASE_NAME = "REL #{PKG_VERSION}"
17
+
18
+ RUBY_FORGE_PROJECT = "feedtools"
19
+ RUBY_FORGE_USER = "sporkmonger"
20
+
21
+ PKG_FILES = FileList[
22
+ '[a-zA-Z]*',
23
+ 'bin/**/*',
24
+ 'lib/**/*',
25
+ 'config/**/*',
26
+ 'example/**/*'
27
+ ].exclude(/\bCVS\b|~$/).exclude(/database\.yml/).exclude(/\.svn$/)
28
+
29
+ desc "Default Task"
30
+ task :default => [ :package ]
31
+
32
+ # Generate the RDoc documentation
33
+
34
+ Rake::RDocTask.new { |rdoc|
35
+ rdoc.rdoc_dir = 'doc'
36
+ rdoc.title = "FeedUpdater -- updater daemon for FeedTools"
37
+ rdoc.options << '--line-numbers' << '--inline-source' <<
38
+ '--accessor' << 'cattr_accessor=object'
39
+ rdoc.template = "#{ENV['template']}.rb" if ENV['template']
40
+ rdoc.rdoc_files.include('README', 'CHANGELOG')
41
+ rdoc.rdoc_files.include('lib/**/*.rb')
42
+ }
43
+
44
+ # Create compressed packages
45
+
46
+ spec = Gem::Specification.new do |s|
47
+ s.name = PKG_NAME
48
+ s.version = PKG_VERSION
49
+ s.summary = "Automatic feed updater daemon."
50
+ s.description = <<-DESC
51
+ Automatic feed updater daemon for use with FeedTools.
52
+ DESC
53
+ s.description.strip!
54
+
55
+ s.files = PKG_FILES.to_a
56
+
57
+ s.add_dependency('feedtools', '>= 0.2.24')
58
+
59
+ s.require_path = 'lib'
60
+ s.autorequire = 'feed_updater'
61
+
62
+ s.rdoc_options << '--exclude' << '.'
63
+ s.has_rdoc = false
64
+
65
+ s.bindir = "bin"
66
+ s.executables = ["feed_updater"]
67
+ s.default_executable = "feed_updater"
68
+
69
+ s.author = "Bob Aman"
70
+ s.email = "bob@sporkmonger.com"
71
+ s.homepage = "http://sporkmonger.com/projects/feedupdater"
72
+ s.rubyforge_project = "feedtools"
73
+ end
74
+
75
+ Rake::GemPackageTask.new(spec) do |p|
76
+ p.gem_spec = spec
77
+ p.need_tar = true
78
+ p.need_zip = true
79
+ end
80
+
81
+ task :lines do
82
+ lines, codelines, total_lines, total_codelines = 0, 0, 0, 0
83
+
84
+ for file_name in FileList["bin/*", "lib/**/*.rb"]
85
+ f = File.open(file_name)
86
+
87
+ while line = f.gets
88
+ lines += 1
89
+ next if line =~ /^\s*$/
90
+ next if line =~ /^\s*#/
91
+ codelines += 1
92
+ end
93
+ puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}"
94
+
95
+ total_lines += lines
96
+ total_codelines += codelines
97
+
98
+ lines, codelines = 0, 0
99
+ end
100
+
101
+ puts "Total: Lines #{total_lines}, LOC #{total_codelines}"
102
+ end
103
+
104
+ # Publishing ------------------------------------------------------
105
+
106
+ desc "Publish the API documentation"
107
+ task :pdoc => [:rdoc] do
108
+ Rake::SshDirPublisher.new(
109
+ "vacindak@sporkmonger.com",
110
+ "public_html/projects/feedupdater/api",
111
+ "doc").upload
112
+ end
113
+
114
+ desc "Publish the release files to RubyForge."
115
+ task :release => [ :gem ] do
116
+ `rubyforge login`
117
+ release_command = "rubyforge add_release feedtools #{PKG_NAME} " +
118
+ "'REL #{PKG_VERSION}' pkg/#{PKG_NAME}-#{PKG_VERSION}.gem"
119
+ puts release_command
120
+ system(release_command)
121
+ end
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11
3
+ specification_version: 1
4
+ name: feedupdater
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.1.0
7
+ date: 2006-04-13 00:00:00 -07:00
8
+ summary: Automatic feed updater daemon.
9
+ require_paths:
10
+ - lib
11
+ email: bob@sporkmonger.com
12
+ homepage: http://sporkmonger.com/projects/feedupdater
13
+ rubyforge_project: feedtools
14
+ description: Automatic feed updater daemon for use with FeedTools.
15
+ autorequire: feed_updater
16
+ default_executable: feed_updater
17
+ bindir: bin
18
+ has_rdoc: false
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
+ - Bob Aman
31
+ files:
32
+ - bin
33
+ - CHANGELOG
34
+ - config
35
+ - doc
36
+ - example
37
+ - lib
38
+ - log
39
+ - rakefile
40
+ - README
41
+ - bin/feed_updater
42
+ - lib/feed_updater
43
+ - lib/feed_updater.rb
44
+ - lib/feed_updater/vendor
45
+ - lib/feed_updater/version.rb
46
+ - lib/feed_updater/vendor/daemons
47
+ - lib/feed_updater/vendor/daemons.rb
48
+ - lib/feed_updater/vendor/daemons/application.rb
49
+ - lib/feed_updater/vendor/daemons/application_group.rb
50
+ - lib/feed_updater/vendor/daemons/cmdline.rb
51
+ - lib/feed_updater/vendor/daemons/controller.rb
52
+ - lib/feed_updater/vendor/daemons/daemonize.rb
53
+ - lib/feed_updater/vendor/daemons/exceptions.rb
54
+ - lib/feed_updater/vendor/daemons/monitor.rb
55
+ - lib/feed_updater/vendor/daemons/pid.rb
56
+ - lib/feed_updater/vendor/daemons/pidfile.rb
57
+ - lib/feed_updater/vendor/daemons/pidmem.rb
58
+ - config/feed_updater.pid
59
+ - config/feed_updater.yml
60
+ - example/custom_updater.rb
61
+ test_files: []
62
+ rdoc_options:
63
+ - "--exclude"
64
+ - "."
65
+ extra_rdoc_files: []
66
+ executables:
67
+ - feed_updater
68
+ extensions: []
69
+ requirements: []
70
+ dependencies:
71
+ - !ruby/object:Gem::Dependency
72
+ name: feedtools
73
+ version_requirement:
74
+ version_requirements: !ruby/object:Gem::Version::Requirement
75
+ requirements:
76
+ -
77
+ - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: 0.2.24
80
+ version: