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.
- data/CHANGELOG +2 -0
- data/README +34 -0
- data/bin/feed_updater +309 -0
- data/config/feed_updater.pid +1 -0
- data/config/feed_updater.yml +12 -0
- data/example/custom_updater.rb +14 -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 +401 -0
- data/rakefile +121 -0
- metadata +80 -0
data/CHANGELOG
ADDED
data/README
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
The FeedUpdater is a simple daemon for handling the automatic updating of
|
2
|
+
FeedTools feed objects.
|
3
|
+
|
4
|
+
USAGE: feed_updater <subcommand>
|
5
|
+
|
6
|
+
Subcommands:
|
7
|
+
start
|
8
|
+
stop
|
9
|
+
restart
|
10
|
+
install
|
11
|
+
|
12
|
+
The FeedUpdater daemon is very easy to use, especially if you're using it
|
13
|
+
with a Rails application. If you're going to be using it with a Rails app,
|
14
|
+
simply install the feedupdater gem, navigate to your Rails app, and run
|
15
|
+
the 'feed_updater install' command. This will install FeedUpdater. Then
|
16
|
+
edit the feed_updater.yml config file as necessary. You will probably need
|
17
|
+
to define a custom updater class, which should probably go in your lib
|
18
|
+
directory. It's recommended that you use the example/custom_updater.rb file
|
19
|
+
as a starting point. You should using the updater events to copy any
|
20
|
+
required fields to feed subscription tables or item tables as necessary so
|
21
|
+
that a full feed parse isn't required every time the feed is displayed.
|
22
|
+
|
23
|
+
The config file also has a start delay option. This should almost always be
|
24
|
+
set to true. You should only turn it off when you are debugging a custom
|
25
|
+
updater class, and it should probably never be turned off in production.
|
26
|
+
Keeping it on prevents FeedUpdater from hitting the CPU too hard immediately
|
27
|
+
after a server reboot, and also prevents hundreds of update processes from
|
28
|
+
kicking off simultaneously on a shared server.
|
29
|
+
|
30
|
+
If you chose to use FeedUpdater outside a Rails application, you should try
|
31
|
+
to use a similar directory structure to what would be found in a Rails
|
32
|
+
setup. FeedUpdater will usually look in the current directory if it cannot
|
33
|
+
find a file its looking for though, so if FeedUpdater can't find a file it
|
34
|
+
needs, changing directories will probably fix the problem.
|
data/bin/feed_updater
ADDED
@@ -0,0 +1,309 @@
|
|
1
|
+
#!/usr/local/bin/ruby
|
2
|
+
|
3
|
+
#--
|
4
|
+
# Copyright (c) 2005 Robert Aman
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
7
|
+
# a copy of this software and associated documentation files (the
|
8
|
+
# "Software"), to deal in the Software without restriction, including
|
9
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
10
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
11
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
12
|
+
# the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be
|
15
|
+
# included in all copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
19
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
21
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
22
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
23
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
24
|
+
#++
|
25
|
+
|
26
|
+
if RUBY_PLATFORM =~ /mswin/
|
27
|
+
puts "FeedUpdater is not supported on Windows due to a lack of fork()."
|
28
|
+
exit
|
29
|
+
end
|
30
|
+
|
31
|
+
# :stopdoc:
|
32
|
+
# ROFLCOPTERS?
|
33
|
+
# This mainly exists because of the approximately 1 billion different
|
34
|
+
# ways to deploy this script.
|
35
|
+
class FileStalker
|
36
|
+
def self.hunt(paths)
|
37
|
+
for path in paths
|
38
|
+
if File.exists?(File.expand_path(path))
|
39
|
+
return File.expand_path(path)
|
40
|
+
elsif File.exists?(File.expand_path(
|
41
|
+
File.dirname(__FILE__) + path))
|
42
|
+
return File.expand_path(File.dirname(__FILE__) + path)
|
43
|
+
elsif File.exists?(File.expand_path(
|
44
|
+
File.dirname(__FILE__) + "/" + path))
|
45
|
+
return File.expand_path(File.dirname(__FILE__) + "/" + path)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
return nil
|
49
|
+
end
|
50
|
+
end
|
51
|
+
# :startdoc:
|
52
|
+
|
53
|
+
$:.unshift(FileStalker.hunt([
|
54
|
+
"/../vendor/feedupdater/lib",
|
55
|
+
"vendor/feedupdater/lib",
|
56
|
+
"../lib"]))
|
57
|
+
$:.uniq!
|
58
|
+
|
59
|
+
require 'yaml'
|
60
|
+
require 'feed_updater'
|
61
|
+
|
62
|
+
if ARGV[0].nil?
|
63
|
+
# Exit quickly if no command line arguments given.
|
64
|
+
|
65
|
+
feed_updater_version = FeedTools::FEED_UPDATER_VERSION::STRING
|
66
|
+
feed_tools_version = "<error: not loaded>"
|
67
|
+
if defined?(FeedTools::FEED_TOOLS_VERSION::STRING)
|
68
|
+
feed_tools_version = FeedTools::FEED_TOOLS_VERSION::STRING
|
69
|
+
elsif defined?(FEED_TOOLS_VERSION)
|
70
|
+
feed_tools_version = FEED_TOOLS_VERSION
|
71
|
+
end
|
72
|
+
puts ""
|
73
|
+
puts "FeedUpdater #{feed_updater_version} / FeedTools #{feed_tools_version}"
|
74
|
+
puts "USAGE: feed_updater <subcommand>"
|
75
|
+
puts ""
|
76
|
+
puts "Subcommands:"
|
77
|
+
puts " start"
|
78
|
+
puts " This will start the daemon if it hasn't already been started."
|
79
|
+
puts ""
|
80
|
+
puts " stop"
|
81
|
+
puts " This will stop the daemon if it's already running."
|
82
|
+
puts ""
|
83
|
+
puts " restart"
|
84
|
+
puts " This will restart the daemon."
|
85
|
+
puts ""
|
86
|
+
puts " install"
|
87
|
+
puts " If the current directory is a Rails installation, this will"
|
88
|
+
puts " install FeedUpdater into the vendor folder and add a new script"
|
89
|
+
puts " to the scripts/ directory for running FeedUpdater. Rerunning"
|
90
|
+
puts " the install subcommand will update the files against the latest"
|
91
|
+
puts " version of the FeedUpdater gem file you have installed. Rails"
|
92
|
+
puts " is NOT required to use FeedUpdater."
|
93
|
+
puts ""
|
94
|
+
exit
|
95
|
+
elsif ARGV[0] == "install"
|
96
|
+
rails_root = nil
|
97
|
+
install_vendor_directory = nil
|
98
|
+
install_config_directory = nil
|
99
|
+
install_script_directory = nil
|
100
|
+
|
101
|
+
current_directory = File.expand_path(".")
|
102
|
+
loop do
|
103
|
+
entries = Dir.entries(current_directory)
|
104
|
+
if entries.include?("vendor") &&
|
105
|
+
entries.include?("config") &&
|
106
|
+
entries.include?("script")
|
107
|
+
rails_root = current_directory
|
108
|
+
break
|
109
|
+
else
|
110
|
+
break if current_directory == "/"
|
111
|
+
current_directory = File.expand_path(current_directory + "/..")
|
112
|
+
end
|
113
|
+
end
|
114
|
+
if rails_root.nil?
|
115
|
+
puts "You must run the install command when the current directory is"
|
116
|
+
puts "a Rails application."
|
117
|
+
else
|
118
|
+
install_vendor_directory = File.expand_path(rails_root + "/vendor")
|
119
|
+
install_config_directory = File.expand_path(rails_root + "/config")
|
120
|
+
install_script_directory = File.expand_path(rails_root + "/script")
|
121
|
+
|
122
|
+
# Install FeedUpdater gem to application vendor directory
|
123
|
+
system("rm -rf #{install_vendor_directory}/feedupdater")
|
124
|
+
unpack_string = `gem unpack feedupdater`
|
125
|
+
feed_updater_version =
|
126
|
+
unpack_string.scan(/\'feedupdater\-(.*)\'/).flatten[0]
|
127
|
+
if feed_updater_version.nil? || feed_updater_version == ""
|
128
|
+
puts "You need to have the feedupdater gem installed for the install"
|
129
|
+
puts "subcommand to run correctly."
|
130
|
+
exit
|
131
|
+
end
|
132
|
+
puts "Installing FeedUpdater #{feed_updater_version} " +
|
133
|
+
"to vendor directory..."
|
134
|
+
|
135
|
+
system("cp -rp feedupdater-#{feed_updater_version} " +
|
136
|
+
"#{install_vendor_directory}")
|
137
|
+
system("rm -rf feedupdater-#{feed_updater_version}")
|
138
|
+
system("mv #{install_vendor_directory}/" +
|
139
|
+
"feedupdater-#{feed_updater_version} " +
|
140
|
+
"#{install_vendor_directory}/feedupdater")
|
141
|
+
|
142
|
+
# Install FeedTools gem to application vendor directory
|
143
|
+
system("rm -rf #{install_vendor_directory}/feedtools")
|
144
|
+
unpack_string = `gem unpack feedtools`
|
145
|
+
feed_tools_version =
|
146
|
+
unpack_string.scan(/\'feedtools\-(.*)\'/).flatten[0]
|
147
|
+
if feed_tools_version.nil? || feed_tools_version == ""
|
148
|
+
puts "You need to have the feedtools gem installed for the install"
|
149
|
+
puts "subcommand to run correctly."
|
150
|
+
exit
|
151
|
+
end
|
152
|
+
puts "Installing FeedTools #{feed_tools_version} to vendor directory..."
|
153
|
+
|
154
|
+
system("cp -rp feedtools-#{feed_tools_version} " +
|
155
|
+
"#{install_vendor_directory}")
|
156
|
+
system("rm -rf feedtools-#{feed_tools_version}")
|
157
|
+
system("mv #{install_vendor_directory}/" +
|
158
|
+
"feedtools-#{feed_tools_version} " +
|
159
|
+
"#{install_vendor_directory}/feedtools")
|
160
|
+
|
161
|
+
# Copying the default config file to the config directory
|
162
|
+
if !File.exists?(File.expand_path(install_config_directory +
|
163
|
+
"/feed_updater.yml"))
|
164
|
+
puts "Installing default config file..."
|
165
|
+
config_file =
|
166
|
+
File.expand_path(File.dirname(__FILE__) + '/../config/feed_updater.yml')
|
167
|
+
system("cp -p #{config_file} #{install_config_directory}")
|
168
|
+
else
|
169
|
+
puts "Config file already exists, skipping..."
|
170
|
+
end
|
171
|
+
|
172
|
+
# Copying this executable to the script directory
|
173
|
+
puts "Copying executable to script directory..."
|
174
|
+
system("rm -rf #{install_script_directory}/feed_updater")
|
175
|
+
system("cp -p #{__FILE__} #{install_script_directory}")
|
176
|
+
system("chmod +x #{install_script_directory}/feed_updater")
|
177
|
+
|
178
|
+
puts "Use script/feed_updater to run FeedUpdater."
|
179
|
+
end
|
180
|
+
exit
|
181
|
+
end
|
182
|
+
|
183
|
+
config_file = FileStalker.hunt([
|
184
|
+
"./feed_updater.yml",
|
185
|
+
"../config/feed_updater.yml"
|
186
|
+
])
|
187
|
+
if config_file.nil?
|
188
|
+
puts "Could not locate feed_updater.yml config file."
|
189
|
+
exit
|
190
|
+
end
|
191
|
+
|
192
|
+
config_hash = YAML.load(File.open(config_file, "r").read)
|
193
|
+
load_script_path = config_hash["load_script"]
|
194
|
+
if !load_script_path.nil?
|
195
|
+
result = FileStalker.hunt([
|
196
|
+
load_script_path,
|
197
|
+
File.dirname(config_file) + "/" + load_script_path,
|
198
|
+
File.dirname(config_file) + load_script_path,
|
199
|
+
File.dirname(__FILE__) + "/../" + load_script_path,
|
200
|
+
File.dirname(__FILE__) + "/.." + load_script_path
|
201
|
+
])
|
202
|
+
if result.nil?
|
203
|
+
puts "Could not locate #{load_script_path.inspect} file."
|
204
|
+
exit
|
205
|
+
else
|
206
|
+
load_script_path = result
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
pid_file_path = config_hash["pid_file_path"]
|
211
|
+
if !pid_file_path.nil?
|
212
|
+
result = FileStalker.hunt([
|
213
|
+
pid_file_path,
|
214
|
+
File.dirname(config_file) + "/" + pid_file_path,
|
215
|
+
File.dirname(config_file) + pid_file_path,
|
216
|
+
File.dirname(__FILE__) + "/../" + pid_file_path,
|
217
|
+
File.dirname(__FILE__) + "/.." + pid_file_path,
|
218
|
+
"../" + pid_file_path
|
219
|
+
])
|
220
|
+
pid_file_path = result if !result.nil?
|
221
|
+
else
|
222
|
+
result = FileStalker.hunt([
|
223
|
+
File.dirname(__FILE__) + "/../config",
|
224
|
+
"./config",
|
225
|
+
File.dirname(__FILE__) + "/config"
|
226
|
+
])
|
227
|
+
pid_file_path = result if !result.nil?
|
228
|
+
end
|
229
|
+
|
230
|
+
log_file_path = config_hash["log_file_path"]
|
231
|
+
if !log_file_path.nil?
|
232
|
+
result = FileStalker.hunt([
|
233
|
+
log_file_path,
|
234
|
+
File.dirname(config_file) + "/" + log_file_path,
|
235
|
+
File.dirname(config_file) + log_file_path,
|
236
|
+
File.dirname(__FILE__) + "/../" + log_file_path,
|
237
|
+
File.dirname(__FILE__) + "/.." + log_file_path,
|
238
|
+
"../" + log_file_path
|
239
|
+
])
|
240
|
+
log_file_path = result if !result.nil?
|
241
|
+
else
|
242
|
+
result = FileStalker.hunt([
|
243
|
+
File.dirname(__FILE__) + "/../log",
|
244
|
+
"./log",
|
245
|
+
File.dirname(__FILE__) + "/log",
|
246
|
+
File.dirname(__FILE__) + "/../logs",
|
247
|
+
"./logs",
|
248
|
+
File.dirname(__FILE__) + "/logs"
|
249
|
+
])
|
250
|
+
log_file_path = result if !result.nil?
|
251
|
+
end
|
252
|
+
|
253
|
+
script_class = nil
|
254
|
+
existing_subclasses = []
|
255
|
+
ObjectSpace.each_object do |instance|
|
256
|
+
if instance.class.name == "Class" &&
|
257
|
+
instance.ancestors.include?(FeedTools::FeedUpdater)
|
258
|
+
existing_subclasses << instance
|
259
|
+
end
|
260
|
+
end
|
261
|
+
load(load_script_path)
|
262
|
+
loaded_subclasses = []
|
263
|
+
ObjectSpace.each_object do |instance|
|
264
|
+
if instance.class.name == "Class" &&
|
265
|
+
instance.ancestors.include?(FeedTools::FeedUpdater)
|
266
|
+
loaded_subclasses << instance
|
267
|
+
end
|
268
|
+
end
|
269
|
+
script_classes = (loaded_subclasses - existing_subclasses)
|
270
|
+
if script_classes.size == 0
|
271
|
+
puts "There do not appear to be any subclasses of FeedTools::FeedUpdater."
|
272
|
+
exit
|
273
|
+
elsif script_classes.size > 1
|
274
|
+
puts "There appear to be multiple subclasses of FeedTools::FeedUpdater."
|
275
|
+
exit
|
276
|
+
else
|
277
|
+
script_class = script_classes[0]
|
278
|
+
end
|
279
|
+
|
280
|
+
updater = nil
|
281
|
+
if load_script_path.nil?
|
282
|
+
updater = FeedTools::FeedUpdater.new
|
283
|
+
FeedTools::FeedUpdater.on_update do |feed, seconds|
|
284
|
+
updater.logger.info("Loaded '#{feed.href}'.")
|
285
|
+
updater.logger.info("=> Updated (#{feed.title}) in #{seconds} seconds.")
|
286
|
+
end
|
287
|
+
FeedTools::FeedUpdater.on_error do |href, error|
|
288
|
+
updater.logger.info("Error updating '#{href}':")
|
289
|
+
updater.logger.info(error)
|
290
|
+
end
|
291
|
+
else
|
292
|
+
updater = script_class.new
|
293
|
+
end
|
294
|
+
|
295
|
+
for key in config_hash.keys
|
296
|
+
key_sym = key.to_s.to_sym
|
297
|
+
updater.updater_options[key_sym] = config_hash[key]
|
298
|
+
end
|
299
|
+
updater.pid_file_dir = pid_file_path
|
300
|
+
updater.log_file_dir = log_file_path
|
301
|
+
|
302
|
+
case ARGV[0]
|
303
|
+
when "start"
|
304
|
+
updater.start()
|
305
|
+
when "stop"
|
306
|
+
updater.stop()
|
307
|
+
when "restart"
|
308
|
+
updater.restart()
|
309
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
617
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# This ensures that you don't break shared servers.
|
2
|
+
start_delay: false
|
3
|
+
|
4
|
+
# This is the path to the custom handler for updated feeds.
|
5
|
+
# See the documentation for details.
|
6
|
+
load_script: example/custom_updater.rb
|
7
|
+
|
8
|
+
# This is the path to store the pid files.
|
9
|
+
pid_file_path: config
|
10
|
+
|
11
|
+
# This is the path to store the log files.
|
12
|
+
log_file_path: log
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class CustomUpdater < FeedTools::FeedUpdater
|
2
|
+
on_update do |feed, seconds|
|
3
|
+
logger.info("Loaded '#{feed.href}'.")
|
4
|
+
logger.info("=> Updated (#{feed.title}) in #{seconds} seconds.")
|
5
|
+
end
|
6
|
+
|
7
|
+
on_error do |href, error|
|
8
|
+
logger.info("Error updating '#{href}':")
|
9
|
+
logger.info(error)
|
10
|
+
end
|
11
|
+
|
12
|
+
on_complete do |updated_feed_hrefs|
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,298 @@
|
|
1
|
+
require 'daemons/pidfile'
|
2
|
+
require 'daemons/pidmem'
|
3
|
+
|
4
|
+
|
5
|
+
module Daemons
|
6
|
+
|
7
|
+
class Application
|
8
|
+
|
9
|
+
attr_accessor :app_argv
|
10
|
+
attr_accessor :controller_argv
|
11
|
+
|
12
|
+
# the Pid instance belonging to this application
|
13
|
+
attr_reader :pid
|
14
|
+
|
15
|
+
# the ApplicationGroup the application belongs to
|
16
|
+
attr_reader :group
|
17
|
+
|
18
|
+
# my private options
|
19
|
+
attr_reader :options
|
20
|
+
|
21
|
+
|
22
|
+
def initialize(group, add_options = {}, pid = nil)
|
23
|
+
@group = group
|
24
|
+
@options = group.options.dup
|
25
|
+
@options.update(add_options)
|
26
|
+
|
27
|
+
unless @pid = pid
|
28
|
+
if dir = pidfile_dir
|
29
|
+
@pid = PidFile.new(pidfile_dir(), @group.app_name, @group.multiple)
|
30
|
+
else
|
31
|
+
@pid = PidMem.new
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def script
|
37
|
+
@script || @group.script
|
38
|
+
end
|
39
|
+
|
40
|
+
def pidfile_dir
|
41
|
+
Pid.dir(@dir_mode || @group.dir_mode, @dir || @group.dir, @script || @group.script)
|
42
|
+
end
|
43
|
+
|
44
|
+
def logfile
|
45
|
+
(options[:log_output] && pidfile_dir()) ? File.join(pidfile_dir(), @group.app_name + '.output') : nil
|
46
|
+
end
|
47
|
+
|
48
|
+
def start_none
|
49
|
+
unless options[:ontop]
|
50
|
+
Daemonize.daemonize #(logfile)
|
51
|
+
else
|
52
|
+
Daemonize.simulate
|
53
|
+
end
|
54
|
+
|
55
|
+
@pid.pid = Process.pid
|
56
|
+
|
57
|
+
|
58
|
+
# We need this to remove the pid-file if the applications exits by itself.
|
59
|
+
# Note that <tt>at_text</tt> will only be run if the applications exits by calling
|
60
|
+
# <tt>exit</tt>, and not if it calls <tt>exit!</tt> (so please don't call <tt>exit!</tt>
|
61
|
+
# in your application!
|
62
|
+
#
|
63
|
+
at_exit {
|
64
|
+
@pid.cleanup rescue nil
|
65
|
+
|
66
|
+
# If the option <tt>:backtrace</tt> is used and the application did exit by itself
|
67
|
+
# create a exception log.
|
68
|
+
if options[:backtrace] and not options[:ontop] and not $daemons_sigterm
|
69
|
+
exception_log() rescue nil
|
70
|
+
end
|
71
|
+
|
72
|
+
}
|
73
|
+
|
74
|
+
# This part is needed to remove the pid-file if the application is killed by
|
75
|
+
# daemons or manually by the user.
|
76
|
+
# Note that the applications is not supposed to overwrite the signal handler for
|
77
|
+
# 'TERM'.
|
78
|
+
#
|
79
|
+
trap('TERM') {
|
80
|
+
@pid.cleanup rescue nil
|
81
|
+
$daemons_sigterm = true
|
82
|
+
|
83
|
+
exit
|
84
|
+
}
|
85
|
+
end
|
86
|
+
|
87
|
+
def start_exec
|
88
|
+
unless options[:ontop]
|
89
|
+
Daemonize.daemonize(logfile)
|
90
|
+
else
|
91
|
+
Daemonize.simulate(logfile)
|
92
|
+
end
|
93
|
+
|
94
|
+
@pid.pid = Process.pid
|
95
|
+
|
96
|
+
ENV['DAEMONS_ARGV'] = @controller_argv.join(' ')
|
97
|
+
# haven't tested yet if this is really passed to the exec'd process...
|
98
|
+
|
99
|
+
Kernel.exec(script(), *ARGV)
|
100
|
+
end
|
101
|
+
|
102
|
+
def start_load
|
103
|
+
unless options[:ontop]
|
104
|
+
Daemonize.daemonize(logfile)
|
105
|
+
else
|
106
|
+
Daemonize.simulate(logfile)
|
107
|
+
end
|
108
|
+
|
109
|
+
@pid.pid = Process.pid
|
110
|
+
|
111
|
+
|
112
|
+
# We need this to remove the pid-file if the applications exits by itself.
|
113
|
+
# Note that <tt>at_text</tt> will only be run if the applications exits by calling
|
114
|
+
# <tt>exit</tt>, and not if it calls <tt>exit!</tt> (so please don't call <tt>exit!</tt>
|
115
|
+
# in your application!
|
116
|
+
#
|
117
|
+
at_exit {
|
118
|
+
@pid.cleanup rescue nil
|
119
|
+
|
120
|
+
# If the option <tt>:backtrace</tt> is used and the application did exit by itself
|
121
|
+
# create a exception log.
|
122
|
+
if options[:backtrace] and not options[:ontop] and not $daemons_sigterm
|
123
|
+
exception_log() rescue nil
|
124
|
+
end
|
125
|
+
|
126
|
+
}
|
127
|
+
|
128
|
+
# This part is needed to remove the pid-file if the application is killed by
|
129
|
+
# daemons or manually by the user.
|
130
|
+
# Note that the applications is not supposed to overwrite the signal handler for
|
131
|
+
# 'TERM'.
|
132
|
+
#
|
133
|
+
trap('TERM') {
|
134
|
+
@pid.cleanup rescue nil
|
135
|
+
$daemons_sigterm = true
|
136
|
+
|
137
|
+
exit
|
138
|
+
}
|
139
|
+
|
140
|
+
# Know we really start the script...
|
141
|
+
$DAEMONS_ARGV = @controller_argv
|
142
|
+
ENV['DAEMONS_ARGV'] = @controller_argv.join(' ')
|
143
|
+
|
144
|
+
ARGV.clear
|
145
|
+
ARGV.concat @app_argv if @app_argv
|
146
|
+
|
147
|
+
# TODO: begin - rescue - end around this and exception logging
|
148
|
+
load script()
|
149
|
+
end
|
150
|
+
|
151
|
+
def start_proc
|
152
|
+
return unless options[:proc]
|
153
|
+
|
154
|
+
unless options[:ontop]
|
155
|
+
@pid.pid = Daemonize.call_as_daemon(options[:proc], logfile)
|
156
|
+
else
|
157
|
+
# Daemonize.simulate(logfile)
|
158
|
+
#
|
159
|
+
# @pid.pid = Process.pid
|
160
|
+
#
|
161
|
+
# Thread.new(&options[:proc])
|
162
|
+
unless @pid.pid = Process.fork
|
163
|
+
Daemonize.simulate(logfile)
|
164
|
+
options[:proc].call
|
165
|
+
exit
|
166
|
+
else
|
167
|
+
Process.detach(@pid.pid)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
def start
|
174
|
+
@group.create_monitor(@group.applications[0] || self)
|
175
|
+
|
176
|
+
case options[:mode]
|
177
|
+
when :none
|
178
|
+
start_none
|
179
|
+
when :exec
|
180
|
+
start_exec
|
181
|
+
when :load
|
182
|
+
start_load
|
183
|
+
when :proc
|
184
|
+
start_proc
|
185
|
+
else
|
186
|
+
start_load
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# def run
|
191
|
+
# if @group.controller.options[:exec]
|
192
|
+
# run_via_exec()
|
193
|
+
# else
|
194
|
+
# run_via_load()
|
195
|
+
# end
|
196
|
+
# end
|
197
|
+
#
|
198
|
+
# def run_via_exec
|
199
|
+
#
|
200
|
+
# end
|
201
|
+
#
|
202
|
+
# def run_via_load
|
203
|
+
#
|
204
|
+
# end
|
205
|
+
|
206
|
+
|
207
|
+
# This is a nice little function for debugging purposes:
|
208
|
+
# In case a multi-threaded ruby script exits due to an uncaught exception
|
209
|
+
# it may be difficult to find out where the exception came from because
|
210
|
+
# one cannot catch exceptions that are thrown in threads other than the main
|
211
|
+
# thread.
|
212
|
+
#
|
213
|
+
# This function searches for all exceptions in memory and outputs them to STDERR
|
214
|
+
# (if it is connected) and to a log file in the pid-file directory.
|
215
|
+
#
|
216
|
+
def exception_log
|
217
|
+
require 'logger'
|
218
|
+
|
219
|
+
l_file = Logger.new(File.join(pidfile_dir(), @group.app_name + '.log'))
|
220
|
+
|
221
|
+
|
222
|
+
# the code below only logs the last exception
|
223
|
+
# e = nil
|
224
|
+
#
|
225
|
+
# ObjectSpace.each_object {|o|
|
226
|
+
# if ::Exception === o
|
227
|
+
# e = o
|
228
|
+
# end
|
229
|
+
# }
|
230
|
+
#
|
231
|
+
# l_file.error e
|
232
|
+
# l_file.close
|
233
|
+
|
234
|
+
# this code logs every exception found in memory
|
235
|
+
ObjectSpace.each_object {|o|
|
236
|
+
if ::Exception === o
|
237
|
+
l_file.error o
|
238
|
+
end
|
239
|
+
}
|
240
|
+
|
241
|
+
l_file.close
|
242
|
+
end
|
243
|
+
|
244
|
+
|
245
|
+
def stop
|
246
|
+
if options[:force] and not running?
|
247
|
+
self.zap
|
248
|
+
return
|
249
|
+
end
|
250
|
+
|
251
|
+
# Catch errors when trying to kill a process that doesn't
|
252
|
+
# exist. This happens when the process quits and hasn't been
|
253
|
+
# restarted by the monitor yet. By catching the error, we allow the
|
254
|
+
# pid file clean-up to occur.
|
255
|
+
begin
|
256
|
+
Process.kill('TERM', @pid.pid)
|
257
|
+
rescue Errno::ESRCH => e
|
258
|
+
puts "#{e} #{@pid.pid}"
|
259
|
+
puts "deleting pid-file."
|
260
|
+
end
|
261
|
+
|
262
|
+
# We try to remove the pid-files by ourselves, in case the application
|
263
|
+
# didn't clean it up.
|
264
|
+
@pid.cleanup rescue nil
|
265
|
+
|
266
|
+
end
|
267
|
+
|
268
|
+
def zap
|
269
|
+
@pid.cleanup
|
270
|
+
end
|
271
|
+
|
272
|
+
def zap!
|
273
|
+
@pid.cleanup rescue nil
|
274
|
+
end
|
275
|
+
|
276
|
+
def show_status
|
277
|
+
running = self.running?
|
278
|
+
|
279
|
+
puts "#{self.group.app_name}: #{running ? '' : 'not '}running#{(running and @pid.exists?) ? ' [pid ' + @pid.pid.to_s + ']' : ''}#{(@pid.exists? and not running) ? ' (but pid-file exists: ' + @pid.pid.to_s + ')' : ''}"
|
280
|
+
end
|
281
|
+
|
282
|
+
# This function implements a (probably too simle) method to detect
|
283
|
+
# whether the program with the pid found in the pid-file is still running.
|
284
|
+
# It just searches for the pid in the output of <tt>ps ax</tt>, which
|
285
|
+
# is probably not a good idea in some cases.
|
286
|
+
# Alternatives would be to use a direct access method the unix process control
|
287
|
+
# system.
|
288
|
+
#
|
289
|
+
def running?
|
290
|
+
if @pid.exists?
|
291
|
+
return Pid.running?(@pid.pid)
|
292
|
+
end
|
293
|
+
|
294
|
+
return false
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
end
|