feedupdater 0.1.0

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.
@@ -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: