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.
@@ -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
@@ -0,0 +1,150 @@
1
+
2
+ module Daemons
3
+ class ApplicationGroup
4
+
5
+ attr_reader :app_name
6
+ attr_reader :script
7
+
8
+ attr_reader :monitor
9
+
10
+ #attr_reader :controller
11
+
12
+ attr_reader :options
13
+
14
+ attr_reader :applications
15
+
16
+ attr_accessor :controller_argv
17
+ attr_accessor :app_argv
18
+
19
+ attr_accessor :dir_mode
20
+ attr_accessor :dir
21
+
22
+ # true if the application is supposed to run in multiple instances
23
+ attr_reader :multiple
24
+
25
+
26
+ def initialize(app_name, options = {})
27
+ @app_name = app_name
28
+ @options = options
29
+
30
+ if options[:script]
31
+ @script = File.expand_path(options[:script])
32
+ end
33
+
34
+ #@controller = controller
35
+ @monitor = nil
36
+
37
+ #options = controller.options
38
+
39
+ @multiple = options[:multiple] || false
40
+
41
+ @dir_mode = options[:dir_mode] || :script
42
+ @dir = options[:dir] || ''
43
+
44
+ #@applications = find_applications(pidfile_dir())
45
+ @applications = []
46
+ end
47
+
48
+ # Setup the application group.
49
+ # Currently this functions calls <tt>find_applications</tt> which finds
50
+ # all running instances of the application and populates the application array.
51
+ #
52
+ def setup
53
+ @applications = find_applications(pidfile_dir())
54
+ end
55
+
56
+ def pidfile_dir
57
+ PidFile.dir(@dir_mode, @dir, script)
58
+ end
59
+
60
+ def find_applications(dir)
61
+ pid_files = PidFile.find_files(dir, app_name)
62
+
63
+ #pp pid_files
64
+
65
+ @monitor = Monitor.find(dir, app_name + '_monitor')
66
+
67
+ pid_files.reject! {|f| f =~ /_monitor.pid$/}
68
+
69
+ return pid_files.map {|f|
70
+ app = Application.new(self, {}, PidFile.existing(f))
71
+ setup_app(app)
72
+ app
73
+ }
74
+ end
75
+
76
+ def new_application(add_options = {})
77
+ if @applications.size > 0 and not @multiple
78
+ if options[:force]
79
+ @applications.delete_if {|a|
80
+ unless a.running?
81
+ a.zap
82
+ true
83
+ end
84
+ }
85
+ end
86
+
87
+ raise RuntimeException.new('there is already one or more instance(s) of the program running') unless @applications.empty?
88
+ end
89
+
90
+ app = Application.new(self, add_options)
91
+
92
+ setup_app(app)
93
+
94
+ @applications << app
95
+
96
+ return app
97
+ end
98
+
99
+ def setup_app(app)
100
+ app.controller_argv = @controller_argv
101
+ app.app_argv = @app_argv
102
+ end
103
+ private :setup_app
104
+
105
+ def create_monitor(an_app)
106
+ return if @monitor
107
+
108
+ if options[:monitor]
109
+ @monitor = Monitor.new(an_app)
110
+
111
+ @monitor.start(@applications)
112
+ end
113
+ end
114
+
115
+ def start_all
116
+ @monitor.stop if @monitor
117
+ @monitor = nil
118
+
119
+ @applications.each {|a|
120
+ fork {
121
+ a.start
122
+ }
123
+ }
124
+ end
125
+
126
+ def stop_all(force = false)
127
+ @monitor.stop if @monitor
128
+
129
+ @applications.each {|a|
130
+ if force
131
+ a.stop rescue nil
132
+ else
133
+ a.stop
134
+ end
135
+ }
136
+ end
137
+
138
+ def zap_all
139
+ @monitor.stop if @monitor
140
+
141
+ @applications.each {|a| a.zap}
142
+ end
143
+
144
+ def show_status
145
+ @applications.each {|a| a.show_status}
146
+ end
147
+
148
+ end
149
+
150
+ end
@@ -0,0 +1,106 @@
1
+
2
+ module Daemons
3
+
4
+ class Optparse
5
+
6
+ attr_reader :usage
7
+
8
+ def initialize(controller)
9
+ @controller = controller
10
+ @options = {}
11
+
12
+ @opts = OptionParser.new do |opts|
13
+ #opts.banner = "Usage: example.rb [options]"
14
+ opts.banner = ""
15
+
16
+ # Boolean switch.
17
+ # opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
18
+ # @options[:verbose] = v
19
+ # end
20
+
21
+ opts.on("-t", "--ontop", "Stay on top (does not daemonize)") do |t|
22
+ @options[:ontop] = t
23
+ end
24
+
25
+ opts.on("-f", "--force", "Force operation") do |t|
26
+ @options[:force] = t
27
+ end
28
+
29
+ #opts.separator ""
30
+ #opts.separator "Specific options:"
31
+
32
+
33
+ opts.separator ""
34
+ opts.separator "Common options:"
35
+
36
+ # No argument, shows at tail. This will print an options summary.
37
+ # Try it and see!
38
+ opts.on_tail("-h", "--help", "Show this message") do
39
+ #puts opts
40
+ #@usage =
41
+ controller.print_usage()
42
+
43
+ exit
44
+ end
45
+
46
+ # Another typical switch to print the version.
47
+ opts.on_tail("--version", "Show version") do
48
+ puts "daemons version #{Daemons::VERSION}"
49
+ exit
50
+ end
51
+ end
52
+
53
+ @usage = @opts.to_s
54
+ end
55
+
56
+
57
+ #
58
+ # Return a hash describing the options.
59
+ #
60
+ def parse(args)
61
+ # The options specified on the command line will be collected in *options*.
62
+ # We set default values here.
63
+ #options = {}
64
+
65
+
66
+ ##pp args
67
+ @opts.parse(args)
68
+
69
+ return @options
70
+ end
71
+
72
+ end
73
+
74
+
75
+ class Controller
76
+
77
+ def print_usage
78
+ puts "Usage: #{@app_name} <command> <options> -- <application options>"
79
+ puts
80
+ puts "* where <command> is one of:"
81
+ puts " start start an instance of the application"
82
+ puts " stop stop all instances of the application"
83
+ puts " restart stop all instances and restart them afterwards"
84
+ puts " run start the application and stay on top"
85
+ puts " zap set the application to a stopped state"
86
+ puts
87
+ puts "* and where <options> may contain several of the following:"
88
+
89
+ puts @optparse.usage
90
+ end
91
+
92
+ def catch_exceptions(&block)
93
+ begin
94
+ block.call
95
+ rescue CmdException, OptionParser::ParseError => e
96
+ puts "ERROR: #{e.to_s}"
97
+ puts
98
+ print_usage()
99
+ rescue RuntimeException => e
100
+ puts "ERROR: #{e.to_s}"
101
+ end
102
+ end
103
+
104
+ end
105
+
106
+ end
@@ -0,0 +1,134 @@
1
+
2
+ module Daemons
3
+ class Controller
4
+
5
+ attr_reader :app_name
6
+
7
+ attr_reader :group
8
+
9
+ attr_reader :options
10
+
11
+
12
+ COMMANDS = [
13
+ 'start',
14
+ 'stop',
15
+ 'restart',
16
+ 'run',
17
+ 'zap',
18
+ 'status'
19
+ ]
20
+
21
+ def initialize(options = {}, argv = [])
22
+ @options = options
23
+ @argv = argv
24
+
25
+ # Allow an app_name to be specified. If not specified use the
26
+ # basename of the script.
27
+ @app_name = options[:app_name]
28
+
29
+ if options[:script]
30
+ @script = File.expand_path(options[:script])
31
+
32
+ @app_name ||= File.split(@script)[1]
33
+ end
34
+
35
+ @app_name = options[:app_name] if options[:app_name]
36
+
37
+ @command, @controller_part, @app_part = Controller.split_argv(argv)
38
+
39
+ #@options[:dir_mode] ||= :script
40
+
41
+ @optparse = Optparse.new(self)
42
+ end
43
+
44
+
45
+ # This function is used to do a final update of the options passed to the application
46
+ # before they are really used.
47
+ #
48
+ # Note that this function should only update <tt>@options</tt> and no other variables.
49
+ #
50
+ def setup_options
51
+ #@options[:ontop] ||= true
52
+ end
53
+
54
+ def run
55
+ @options.update @optparse.parse(@controller_part).delete_if {|k,v| !v}
56
+
57
+ setup_options()
58
+
59
+ #pp @options
60
+
61
+ @group = ApplicationGroup.new(@app_name, @options)
62
+ @group.controller_argv = @controller_part
63
+ @group.app_argv = @app_part
64
+
65
+ @group.setup
66
+
67
+ case @command
68
+ when 'start'
69
+ @group.new_application.start
70
+ when 'run'
71
+ @options[:ontop] ||= true
72
+ @group.new_application.start
73
+ when 'stop'
74
+ @group.stop_all
75
+ when 'restart'
76
+ unless @group.applications.empty?
77
+ @group.stop_all
78
+ sleep 1
79
+ @group.start_all
80
+ end
81
+ when 'zap'
82
+ @group.zap_all
83
+ when 'status'
84
+ unless @group.applications.empty?
85
+ @group.show_status
86
+ else
87
+ puts "#{@group.app_name}: no instances running"
88
+ end
89
+ when nil
90
+ raise CmdException.new('no command given')
91
+ #puts "ERROR: No command given"; puts
92
+
93
+ #print_usage()
94
+ #raise('usage function not implemented')
95
+ else
96
+ raise Error.new("command '#{@command}' not implemented")
97
+ end
98
+ end
99
+
100
+
101
+ # Split an _argv_ array.
102
+ # +argv+ is assumed to be in the following format:
103
+ # ['command', 'controller option 1', 'controller option 2', ..., '--', 'app option 1', ...]
104
+ #
105
+ # <tt>command</tt> must be one of the commands listed in <tt>COMMANDS</tt>
106
+ #
107
+ # *Returns*: the command as a string, the controller options as an array, the appliation options
108
+ # as an array
109
+ #
110
+ def Controller.split_argv(argv)
111
+ argv = argv.dup
112
+
113
+ command = nil
114
+ controller_part = []
115
+ app_part = []
116
+
117
+ if COMMANDS.include? argv[0]
118
+ command = argv.shift
119
+ end
120
+
121
+ if i = argv.index('--')
122
+ # Handle the case where no controller options are given, just
123
+ # options after "--" as well (i == 0)
124
+ controller_part = (i == 0 ? [] : argv[0..i-1])
125
+ app_part = argv[i+1..-1]
126
+ else
127
+ controller_part = argv[0..-1]
128
+ end
129
+
130
+ return command, controller_part, app_part
131
+ end
132
+ end
133
+
134
+ end