openwferu-scheduler 0.9.7

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,244 @@
1
+ #
2
+ #--
3
+ # Copyright (c) 2005-2007, John Mettraux, OpenWFE.org
4
+ # All rights reserved.
5
+ #
6
+ # Redistribution and use in source and binary forms, with or without
7
+ # modification, are permitted provided that the following conditions are met:
8
+ #
9
+ # . Redistributions of source code must retain the above copyright notice, this
10
+ # list of conditions and the following disclaimer.
11
+ #
12
+ # . Redistributions in binary form must reproduce the above copyright notice,
13
+ # this list of conditions and the following disclaimer in the documentation
14
+ # and/or other materials provided with the distribution.
15
+ #
16
+ # . Neither the name of the "OpenWFE" nor the names of its contributors may be
17
+ # used to endorse or promote products derived from this software without
18
+ # specific prior written permission.
19
+ #
20
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
24
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30
+ # POSSIBILITY OF SUCH DAMAGE.
31
+ #++
32
+ #
33
+ # $Id: otime.rb 3509 2006-10-21 12:00:52Z jmettraux $
34
+ #
35
+
36
+ #
37
+ # "hecho en Costa Rica"
38
+ #
39
+ # john.mettraux@openwfe.org
40
+ #
41
+
42
+ require 'date'
43
+ #require 'parsedate'
44
+
45
+
46
+ module OpenWFE
47
+
48
+ #TIME_FORMAT = "%Y-%m-%d %H:%M:%S"
49
+
50
+ #
51
+ # Returns the current time as an ISO date string
52
+ #
53
+ def OpenWFE.now ()
54
+ return to_iso8601_date(Time.new())
55
+ end
56
+
57
+ def OpenWFE.to_iso8601_date (date)
58
+
59
+ if date.kind_of? Float
60
+ date = to_datetime(Time.at(date))
61
+ elsif date.kind_of? Time
62
+ date = to_datetime(date)
63
+ elsif not date.kind_of? Date
64
+ date = DateTime.parse(date)
65
+ end
66
+
67
+ s = date.to_s
68
+ s[10] = " "
69
+
70
+ return s
71
+ end
72
+
73
+ #
74
+ # the old method we used to generate our ISO datetime strings
75
+ #
76
+ def OpenWFE.time_to_iso8601_date (time)
77
+
78
+ s = time.getutc().strftime(TIME_FORMAT)
79
+ o = time.utc_offset / 3600
80
+ o = o.to_s + "00"
81
+ o = "0" + o if o.length < 4
82
+ o = "+" + o unless o[0..1] == '-'
83
+
84
+ s + " " + o.to_s
85
+ end
86
+
87
+ #
88
+ # Returns a Ruby time
89
+ #
90
+ def OpenWFE.to_ruby_time (iso_date)
91
+
92
+ return DateTime.parse(iso_date)
93
+ end
94
+
95
+ #def OpenWFE.parse_date (date)
96
+ #end
97
+
98
+ #
99
+ # equivalent to java.lang.System.currentTimeMillis()
100
+ #
101
+ def OpenWFE.current_time_millis ()
102
+
103
+ t = Time.new()
104
+ t = t.to_f * 1000
105
+ return t.to_i
106
+ end
107
+
108
+ #
109
+ # turns a string like '1m10s' into a float like '70.0'
110
+ #
111
+ # w -> week
112
+ # d -> day
113
+ # h -> hour
114
+ # m -> minute
115
+ # s -> second
116
+ # M -> month
117
+ # y -> year
118
+ # 'nada' -> millisecond
119
+ #
120
+ def OpenWFE.parse_time_string (string)
121
+
122
+ string = string.strip
123
+
124
+ index = -1
125
+ result = 0.0
126
+
127
+ number = ""
128
+
129
+ while true
130
+ index = index + 1
131
+
132
+ if index >= string.length
133
+ if number.length > 0
134
+ result = result + (Float(number) / 1000.0)
135
+ end
136
+ break
137
+ end
138
+
139
+ c = string[index, 1]
140
+
141
+ if is_digit?(c)
142
+ number = number + c
143
+ next
144
+ end
145
+
146
+ value = Integer(number)
147
+ number = ""
148
+
149
+ multiplier = DURATIONS[c]
150
+
151
+ raise "unknown time char '#{c}'" \
152
+ if not multiplier
153
+
154
+ result = result + (value * multiplier)
155
+ end
156
+
157
+ return result
158
+ end
159
+
160
+ #
161
+ # returns true if the character c is a digit
162
+ #
163
+ def OpenWFE.is_digit? (c)
164
+ return false if not c.kind_of?(String)
165
+ return false if c.length > 1
166
+ return (c >= "0" and c <= "9")
167
+ end
168
+
169
+ #
170
+ # conversion methods between Date[Time] and Time
171
+
172
+ #
173
+ # Ruby Cookbook 1st edition p.111
174
+ # http://www.oreilly.com/catalog/rubyckbk/
175
+ # a must
176
+ #
177
+
178
+ #
179
+ # converts a Time instance to a DateTime one
180
+ #
181
+ def OpenWFE.to_datetime (time)
182
+
183
+ s = time.sec + Rational(time.usec, 10**6)
184
+ o = Rational(time.utc_offset, 3600 * 24)
185
+
186
+ begin
187
+
188
+ return DateTime.new(
189
+ time.year,
190
+ time.month,
191
+ time.day,
192
+ time.hour,
193
+ time.min,
194
+ s,
195
+ o)
196
+
197
+ rescue Exception => e
198
+
199
+ #puts
200
+ #puts OpenWFE::exception_to_s(e)
201
+ #puts
202
+ #puts \
203
+ # "\n Date.new() problem. Params :"+
204
+ # "\n....y:#{time.year} M:#{time.month} d:#{time.day} "+
205
+ # "h:#{time.hour} m:#{time.min} s:#{s} o:#{o}"
206
+
207
+ return DateTime.new(
208
+ time.year,
209
+ time.month,
210
+ time.day,
211
+ time.hour,
212
+ time.min,
213
+ time.sec,
214
+ time.utc_offset)
215
+ end
216
+ end
217
+
218
+ def OpenWFE.to_gm_time (dtime)
219
+ to_ttime(dtime.new_offset, :gm)
220
+ end
221
+
222
+ def OpenWFE.to_local_time (dtime)
223
+ to_ttime(dtime.new_offset(DateTime.now.offset-offset), :local)
224
+ end
225
+
226
+ def to_ttime (d, method)
227
+ usec = (d.sec_fraction * 3600 * 24 * (10**6)).to_i
228
+ Time.send(method, d.year, d.month, d.day, d.hour, d.min, d.sec, usec)
229
+ end
230
+
231
+ protected
232
+
233
+ DURATIONS = {
234
+ "y" => 365 * 24 * 3600,
235
+ "M" => 30 * 24 * 3600,
236
+ "w" => 7 * 24 * 3600,
237
+ "d" => 24 * 3600,
238
+ "h" => 3600,
239
+ "m" => 60,
240
+ "s" => 1
241
+ }
242
+
243
+ end
244
+
@@ -0,0 +1,840 @@
1
+ #
2
+ #--
3
+ # Copyright (c) 2006-2007, John Mettraux, OpenWFE.org
4
+ # All rights reserved.
5
+ #
6
+ # Redistribution and use in source and binary forms, with or without
7
+ # modification, are permitted provided that the following conditions are met:
8
+ #
9
+ # . Redistributions of source code must retain the above copyright notice, this
10
+ # list of conditions and the following disclaimer.
11
+ #
12
+ # . Redistributions in binary form must reproduce the above copyright notice,
13
+ # this list of conditions and the following disclaimer in the documentation
14
+ # and/or other materials provided with the distribution.
15
+ #
16
+ # . Neither the name of the "OpenWFE" nor the names of its contributors may be
17
+ # used to endorse or promote products derived from this software without
18
+ # specific prior written permission.
19
+ #
20
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
24
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30
+ # POSSIBILITY OF SUCH DAMAGE.
31
+ #++
32
+ #
33
+ # $Id: definitions.rb 2725 2006-06-02 13:26:32Z jmettraux $
34
+ #
35
+
36
+ #
37
+ # "made in Japan"
38
+ #
39
+ # John Mettraux at openwfe.org
40
+ #
41
+
42
+ require 'monitor'
43
+
44
+ require 'openwfe/util/otime'
45
+
46
+
47
+ module OpenWFE
48
+
49
+ #
50
+ # The Scheduler is used by OpenWFEru for registering 'at' and 'cron' jobs.
51
+ # 'at' jobs to execute once at a given point in time. 'cron' jobs
52
+ # execute a specified intervals.
53
+ # The two main methods are thus schedule_at() and schedule().
54
+ #
55
+ # schedule_at() and schedule() await either a Schedulable instance and
56
+ # params (usually an array or nil), either a block, which is more in the
57
+ # Ruby way.
58
+ #
59
+ # Two examples :
60
+ #
61
+ # scheduler.schedule_in("3d") do
62
+ # regenerate_monthly_report()
63
+ # end
64
+ # #
65
+ # # will call the regenerate_monthly_report method
66
+ # # in 3 days from now
67
+ #
68
+ # and
69
+ #
70
+ # class Regenerator < Schedulable
71
+ # def trigger (frequency)
72
+ # self.send(frequency)
73
+ # end
74
+ # def monthly
75
+ # # ...
76
+ # end
77
+ # def yearly
78
+ # # ...
79
+ # end
80
+ # end
81
+ #
82
+ # regenerator = Regenerator.new
83
+ #
84
+ # scheduler.schedule_in("4d", regenerator, :monthly)
85
+ # #
86
+ # # will regenerate the monthly report in four days
87
+ #
88
+ # There is also schedule_every() :
89
+ #
90
+ # scheduler.schedule_every("1h20m") do
91
+ # regenerate_latest_report()
92
+ # end
93
+ #
94
+ # The scheduler has a "exit_when_no_more_jobs" attribute. When set to
95
+ # 'true', the scheduler will exit as soon as there are no more jobs to
96
+ # run.
97
+ # Use with care though, if you create a scheduler, set this attribute
98
+ # to true and start the scheduler, the scheduler will immediately exit.
99
+ # This attribute is best used indirectly : the method
100
+ # join_until_no_more_jobs() wraps it.
101
+ #
102
+ class Scheduler
103
+ include MonitorMixin
104
+
105
+ attr_accessor \
106
+ :precision,
107
+ :exit_when_no_more_jobs
108
+
109
+ def initialize
110
+
111
+ super()
112
+
113
+ @pending_jobs = []
114
+ @cron_entries = {}
115
+
116
+ @scheduler_thread = nil
117
+
118
+ @precision = 0.250
119
+ #
120
+ # every 250ms, the scheduler wakes up
121
+
122
+ @exit_when_no_more_jobs = false
123
+ @dont_reschedule_every = false
124
+
125
+ @last_cron_minute = -1
126
+
127
+ @stopped = false
128
+ end
129
+
130
+ #
131
+ # Starts this scheduler (or restart it if it was previously stopped)
132
+ #
133
+ def sstart
134
+
135
+ @scheduler_thread = Thread.new do
136
+ while true
137
+ break if @stopped
138
+ step
139
+ sleep(@precision)
140
+ end
141
+ end
142
+ end
143
+
144
+ #
145
+ # The scheduler is stoppable via sstop()
146
+ #
147
+ def sstop
148
+
149
+ @stopped = true
150
+ end
151
+
152
+ alias :start :sstart
153
+ alias :stop :sstop
154
+
155
+ #
156
+ # Joins on the scheduler thread
157
+ #
158
+ def join
159
+
160
+ @scheduler_thread.join
161
+ end
162
+
163
+ #
164
+ # Like join() but takes care of setting the 'exit_when_no_more_jobs'
165
+ # attribute of this scheduler to true before joining.
166
+ # Thus the scheduler will exit (and the join terminates) as soon as
167
+ # there aren't no more 'at' (or 'every') jobs in the scheduler.
168
+ #
169
+ # Currently used only in unit tests.
170
+ #
171
+ def join_until_no_more_jobs
172
+
173
+ @exit_when_no_more_jobs = true
174
+ join
175
+ end
176
+
177
+ #
178
+ # Schedules a job by specifying at which time it should trigger.
179
+ # Returns the a job_id that can be used to unschedule the job.
180
+ #
181
+ def schedule_at (at, schedulable=nil, params=nil, &block)
182
+
183
+ sschedule_at(false, at, nil, schedulable, params, &block)
184
+ end
185
+
186
+
187
+ #
188
+ # Schedules a job by stating in how much time it should trigger.
189
+ # Returns the a job_id that can be used to unschedule the job.
190
+ #
191
+ def schedule_in (duration, schedulable=nil, params=nil, &block)
192
+
193
+ duration = duration_to_f(duration)
194
+
195
+ return schedule_at(
196
+ Time.new.to_f + duration, schedulable, params, &block)
197
+ end
198
+
199
+ #
200
+ # Schedules a job in a loop. After an execution, it will not execute
201
+ # before the time specified in 'freq'.
202
+ #
203
+ # Note that if your job takes 2s to execute and the freq is set to
204
+ # 10s, it will in fact execute every 12s.
205
+ # You can however wrap the code within its own thread :
206
+ #
207
+ # scheduler.schedule_every("12s") do
208
+ # Thread.new do
209
+ # do_the_job()
210
+ # end
211
+ # end
212
+ #
213
+ def schedule_every (freq, schedulable=nil, params=nil, &block)
214
+
215
+ sschedule_every(freq, nil, schedulable, params, &block)
216
+ end
217
+
218
+ #
219
+ # Unschedules an 'at' or a 'cron' job identified by the id
220
+ # it was given at schedule time.
221
+ #
222
+ def unschedule (job_id)
223
+ synchronize do
224
+
225
+ for i in 0...@pending_jobs.length
226
+ if @pending_jobs[i].eid == job_id
227
+ @pending_jobs.delete_at(i)
228
+ return true
229
+ end
230
+ end
231
+
232
+ return true if unschedule_cron_job(job_id)
233
+
234
+ return false
235
+ end
236
+ end
237
+
238
+ #
239
+ # Unschedules a cron job
240
+ #
241
+ def unschedule_cron_job (job_id)
242
+ synchronize do
243
+ if @cron_entries.has_key?(job_id)
244
+ @cron_entries.delete(job_id)
245
+ return true
246
+ end
247
+ return false
248
+ end
249
+ end
250
+
251
+ #
252
+ # Schedules a cron job, the 'cron_line' is a string
253
+ # following the Unix cron standard (see "man 5 crontab" in your command
254
+ # line).
255
+ #
256
+ # For example :
257
+ #
258
+ # scheduler.schedule("5 0 * * *", nil, s, p)
259
+ # # will trigger the schedulable s with params p every day
260
+ # # five minutes after midnight
261
+ #
262
+ # scheduler.schedule("15 14 1 * *", nil, s, p)
263
+ # # will trigger s at 14:15 on the first of every month
264
+ #
265
+ # scheduler.schedule("0 22 * * 1-5") do
266
+ # puts "it's break time..."
267
+ # end
268
+ # # outputs a message every weekday at 10pm
269
+ #
270
+ # Returns the job id attributed to this 'cron job', this id can
271
+ # be used to unschedule the job.
272
+ #
273
+ def schedule (
274
+ cron_line, cron_id=nil, schedulable=nil, params=nil, &block)
275
+
276
+ synchronize do
277
+
278
+ #
279
+ # is a job with the same id already scheduled ?
280
+
281
+ if cron_id and unschedule(cron_id)
282
+ ldebug do
283
+ "schedule() unscheduled previous job "+
284
+ "under same name '#{cron_id}'"
285
+ end
286
+ end
287
+
288
+ #
289
+ # schedule
290
+
291
+ b = to_block(schedulable, params, &block)
292
+ entry = CronEntry.new(cron_id, cron_line, &b)
293
+ @cron_entries[entry.eid] = entry
294
+
295
+ return entry.eid
296
+ end
297
+ end
298
+
299
+ #
300
+ # Returns the job corresponding to job_id, an instance of AtEntry
301
+ # or CronEntry will be returned.
302
+ #
303
+ def get_job (job_id)
304
+
305
+ entry = @cron_entries[job_id]
306
+ return entry if entry
307
+
308
+ @pending_jobs.each do |entry|
309
+ return entry if entry.eid == job_id
310
+ end
311
+
312
+ return nil
313
+ end
314
+
315
+ #
316
+ # Finds a job (via get_job()) and then returns the wrapped
317
+ # schedulable if any.
318
+ #
319
+ def get_schedulable (job_id)
320
+
321
+ return nil unless job_id
322
+
323
+ j = get_job(job_id)
324
+
325
+ return j.schedulable if j.respond_to? :schedulable
326
+ return nil
327
+ end
328
+
329
+ #
330
+ # Returns the number of currently pending jobs in this scheduler
331
+ # ('at' jobs and 'every' jobs).
332
+ #
333
+ def pending_job_count
334
+ @pending_jobs.size
335
+ end
336
+
337
+ #
338
+ # Returns the number of cron jobs currently active in this scheduler.
339
+ #
340
+ def cron_job_count
341
+ @cron_entries.size
342
+ end
343
+
344
+ #
345
+ # Returns the current count of 'every' jobs scheduled.
346
+ #
347
+ def every_job_count
348
+ @pending_jobs.select { |j| j.is_a?(EveryEntry) }.size
349
+ end
350
+
351
+ #
352
+ # Returns the current count of 'at' jobs scheduled (not 'every').
353
+ #
354
+ def at_job_count
355
+ @pending_jobs.select { |j| j.instance_of?(AtEntry) }.size
356
+ end
357
+
358
+ #
359
+ # Returns true if the given string seems to be a cron string.
360
+ #
361
+ def Scheduler.is_cron_string (s)
362
+ return s.match(".+ .+ .+ .+ .+")
363
+ end
364
+
365
+ protected
366
+
367
+ def sschedule_at (
368
+ is_every, at, at_id, schedulable=nil, params=nil, &block)
369
+
370
+ synchronize do
371
+
372
+ #puts "0 at is '#{at.to_s}' (#{at.class})"
373
+
374
+ at = OpenWFE::to_ruby_time(at) \
375
+ if at.kind_of? String
376
+
377
+ at = OpenWFE::to_gm_time(at) \
378
+ if at.kind_of? DateTime
379
+
380
+ at = at.to_f \
381
+ if at.kind_of? Time
382
+
383
+ #puts "1 at is '#{at.to_s}' (#{at.class})"}"
384
+
385
+ jobClass = AtEntry
386
+ jobClass = EveryEntry if is_every
387
+
388
+ b = to_block(schedulable, params, &block)
389
+ job = jobClass.new(at, at_id, &b)
390
+
391
+ if at < (Time.new.to_f + @precision)
392
+ job.trigger()
393
+ return nil
394
+ end
395
+
396
+ return push(job) \
397
+ if @pending_jobs.length < 1
398
+
399
+ # shortcut : check if the new job is posterior to
400
+ # the last job pending
401
+
402
+ return push(job) \
403
+ if at >= @pending_jobs.last.at
404
+
405
+ for i in 0...@pending_jobs.length
406
+ if at <= @pending_jobs[i].at
407
+ return push(job, i)
408
+ end
409
+ end
410
+
411
+ return push(job)
412
+ end
413
+ end
414
+
415
+ def sschedule_every (freq, at_id, schedulable, params, &block)
416
+
417
+ f = duration_to_f(freq)
418
+
419
+ job_id = sschedule_at(
420
+ true, Time.new.to_f + f, at_id) do |eid, at|
421
+
422
+ if schedulable
423
+ schedulable.trigger(params)
424
+ else
425
+ block.call eid, at
426
+ end
427
+
428
+ sschedule_every(f, eid, schedulable, params, &block) \
429
+ unless @dont_reschedule_every
430
+ end
431
+
432
+ job_id
433
+ end
434
+
435
+ #
436
+ # Ensures that a duration is a expressed as a Float instance.
437
+ #
438
+ # duration_to_f("10s")
439
+ #
440
+ # will yields 10.0
441
+ #
442
+ def duration_to_f (s)
443
+ return s if s.kind_of? Float
444
+ return OpenWFE::parse_time_string(s) if s.kind_of? String
445
+ return Float(s.to_s)
446
+ end
447
+
448
+ def to_block (schedulable, params, &block)
449
+ if schedulable
450
+ l = lambda do
451
+ schedulable.trigger(params)
452
+ end
453
+ class << l
454
+ attr_accessor :schedulable
455
+ end
456
+ l.schedulable = schedulable
457
+ l
458
+ else
459
+ block
460
+ end
461
+ end
462
+
463
+ #
464
+ # Pushes an 'at' job into the pending job list
465
+ #
466
+ def push (job, index=-1)
467
+
468
+ if index == -1
469
+ #
470
+ # push job at the end
471
+ #
472
+ @pending_jobs << job
473
+ else
474
+ #
475
+ # insert job at given index
476
+ #
477
+ @pending_jobs[index, 0] = job
478
+ end
479
+
480
+ #puts "push() at '#{Time.at(job.at)}'"
481
+
482
+ return job.eid
483
+ end
484
+
485
+ #
486
+ # This is the method called each time the scheduler wakes up
487
+ # (by default 4 times per second). It's meant to quickly
488
+ # determine if there are jobs to trigger else to get back to sleep.
489
+ # 'cron' jobs get executed if necessary then 'at' jobs.
490
+ #
491
+ def step
492
+ synchronize do
493
+
494
+ now = Time.new
495
+ minute = now.min
496
+
497
+ if @exit_when_no_more_jobs
498
+
499
+ if @pending_jobs.size < 1
500
+
501
+ @stopped = true
502
+ return
503
+ end
504
+
505
+ @dont_reschedule_every = true if at_job_count < 1
506
+ end
507
+
508
+ #
509
+ # cron entries
510
+
511
+ if now.sec == 0 and minute > @last_cron_minute
512
+ #
513
+ # only consider cron entries at the second 0 of a
514
+ # minute
515
+
516
+ @last_cron_minute = minute
517
+
518
+ @cron_entries.each do |cron_id, cron_entry|
519
+ #puts "step() cron_id : #{cron_id}"
520
+ trigger(cron_entry) if cron_entry.matches? now
521
+ end
522
+ end
523
+
524
+ #
525
+ # pending jobs
526
+
527
+ now = now.to_f
528
+ #
529
+ # that's what at jobs do understand
530
+
531
+ while true
532
+
533
+ #puts "step() job.count is #{@pending_jobs.length}"
534
+
535
+ break if @pending_jobs.length < 1
536
+
537
+ job = @pending_jobs[0]
538
+
539
+ #puts "step() job.at is #{job.at}"
540
+ #puts "step() now is #{now}"
541
+
542
+ break if job.at > now
543
+
544
+ #if job.at <= now
545
+ #
546
+ # obviously
547
+
548
+ trigger(job)
549
+
550
+ @pending_jobs.delete_at(0)
551
+ end
552
+ end
553
+ end
554
+
555
+ def trigger (entry)
556
+ Thread.new do
557
+ begin
558
+ entry.trigger
559
+ rescue Exception => e
560
+ message =
561
+ "trigger() caught exception\n" +
562
+ OpenWFE::exception_to_s(e)
563
+ if self.respond_to? :lwarn
564
+ lwarn { message }
565
+ else
566
+ puts message
567
+ end
568
+ end
569
+ end
570
+ end
571
+ end
572
+
573
+ #
574
+ # This module adds a trigger method to any class that includes it.
575
+ # The default implementation feature here triggers an exception.
576
+ #
577
+ module Schedulable
578
+
579
+ def trigger (params)
580
+ raise "trigger() implementation is missing"
581
+ end
582
+
583
+ def reschedule (scheduler)
584
+ raise "reschedule() implentation is missing"
585
+ end
586
+ end
587
+
588
+ protected
589
+
590
+ JOB_ID_LOCK = Monitor.new
591
+
592
+ class Entry
593
+
594
+ @@last_given_id = 0
595
+ #
596
+ # as a scheduler is fully transient, no need to
597
+ # have persistent ids, a simple counter is sufficient
598
+
599
+ attr_accessor \
600
+ :eid, :block
601
+
602
+ def initialize (entry_id=nil, &block)
603
+ @block = block
604
+ if entry_id
605
+ @eid = entry_id
606
+ else
607
+ JOB_ID_LOCK.synchronize do
608
+ @eid = @@last_given_id
609
+ @@last_given_id = @eid + 1
610
+ end
611
+ end
612
+ end
613
+
614
+ #def trigger
615
+ # @block.call @eid
616
+ #end
617
+ end
618
+
619
+ class AtEntry < Entry
620
+
621
+ attr_accessor \
622
+ :at
623
+
624
+ def initialize (at, at_id, &block)
625
+ super(at_id, &block)
626
+ @at = at
627
+ end
628
+
629
+ def trigger
630
+ @block.call @eid, @at
631
+ end
632
+ end
633
+
634
+ class EveryEntry < AtEntry
635
+ end
636
+
637
+ class CronEntry < Entry
638
+
639
+ attr_accessor \
640
+ :cron_line
641
+
642
+ def initialize (cron_id, line, &block)
643
+
644
+ super(cron_id, &block)
645
+
646
+ if line.kind_of? String
647
+ @cron_line = CronLine.new(line)
648
+ elsif line.kind_of? CronLine
649
+ @cron_line = line
650
+ else
651
+ raise \
652
+ "Cannot initialize a CronEntry " +
653
+ "with a param of class #{line.class}"
654
+ end
655
+ end
656
+
657
+ def matches? (time)
658
+ @cron_line.matches? time
659
+ end
660
+
661
+ def trigger
662
+ @block.call @eid, @cron_line
663
+ end
664
+ end
665
+
666
+ #
667
+ # A 'cron line' is a line in the sense of a crontab
668
+ # (man 5 crontab) file line.
669
+ #
670
+ class CronLine
671
+
672
+ attr_reader \
673
+ :minutes,
674
+ :hours,
675
+ :days,
676
+ :months,
677
+ :weekdays
678
+
679
+ def initialize (line)
680
+
681
+ super()
682
+
683
+ items = line.split
684
+
685
+ if items.length != 5
686
+ raise \
687
+ "cron '#{line}' string should hold 5 items, " +
688
+ "not #{items.length}" \
689
+ end
690
+
691
+ @minutes = parse_item(items[0], 0, 59)
692
+ @hours = parse_item(items[1], 0, 24)
693
+ @days = parse_item(items[2], 1, 31)
694
+ @months = parse_item(items[3], 1, 12)
695
+ @weekdays = parse_weekdays(items[4])
696
+
697
+ adjust_arrays()
698
+ end
699
+
700
+ def matches? (time)
701
+
702
+ if time.kind_of?(Float) or time.kind_of?(Integer)
703
+ time = Time.at(time)
704
+ end
705
+
706
+ return false if no_match?(time.min, @minutes)
707
+ return false if no_match?(time.hour, @hours)
708
+ return false if no_match?(time.day, @days)
709
+ return false if no_match?(time.month, @months)
710
+ return false if no_match?(time.wday, @weekdays)
711
+
712
+ return true
713
+ end
714
+
715
+ #
716
+ # Returns an array of 5 arrays (minutes, hours, days, months,
717
+ # weekdays).
718
+ # This method is used by the cronline unit tests.
719
+ #
720
+ def to_array
721
+ [ @minutes, @hours, @days, @months, @weekdays ]
722
+ end
723
+
724
+ private
725
+
726
+ #
727
+ # adjust values to Ruby
728
+ #
729
+ def adjust_arrays()
730
+ if @hours
731
+ @hours.each do |h|
732
+ h = 0 if h == 23
733
+ end
734
+ end
735
+ if @weekdays
736
+ @weekdays.each do |wd|
737
+ wd = wd - 1
738
+ end
739
+ end
740
+ end
741
+
742
+ WDS = [ "mon", "tue", "wed", "thu", "fri", "sat", "sun" ]
743
+ #
744
+ # used by parse_weekday()
745
+
746
+ def parse_weekdays (item)
747
+
748
+ item = item.downcase
749
+
750
+ WDS.each_with_index do |day, index|
751
+ item = item.gsub(day, "#{index+1}")
752
+ end
753
+
754
+ return parse_item(item, 1, 7)
755
+ end
756
+
757
+ def parse_item (item, min, max)
758
+
759
+ return nil \
760
+ if item == "*"
761
+ return parse_list(item, min, max) \
762
+ if item.index(",")
763
+ return parse_range(item, min, max) \
764
+ if item.index("*") or item.index("-")
765
+
766
+ i = Integer(item)
767
+
768
+ i = min if i < min
769
+ i = max if i > max
770
+
771
+ return [ i ]
772
+ end
773
+
774
+ def parse_list (item, min, max)
775
+ items = item.split(",")
776
+ result = []
777
+ items.each do |i|
778
+ i = Integer(i)
779
+ i = min if i < min
780
+ i = max if i > max
781
+ result << i
782
+ end
783
+ return result
784
+ end
785
+
786
+ def parse_range (item, min, max)
787
+ i = item.index("-")
788
+ j = item.index("/")
789
+
790
+ inc = 1
791
+
792
+ inc = Integer(item[j+1..-1]) if j
793
+
794
+ istart = -1
795
+ iend = -1
796
+
797
+ if i
798
+
799
+ istart = Integer(item[0..i-1])
800
+
801
+ if j
802
+ iend = Integer(item[i+1..j])
803
+ else
804
+ iend = Integer(item[i+1..-1])
805
+ end
806
+
807
+ else # case */x
808
+ istart = min
809
+ iend = max
810
+ end
811
+
812
+ istart = min if istart < min
813
+ iend = max if iend > max
814
+
815
+ result = []
816
+
817
+ value = istart
818
+ while true
819
+ result << value
820
+ value = value + inc
821
+ break if value > iend
822
+ end
823
+
824
+ return result
825
+ end
826
+
827
+ def no_match? (value, cron_values)
828
+
829
+ return false if not cron_values
830
+
831
+ cron_values.each do |v|
832
+ return false if value == v
833
+ end
834
+
835
+ return true
836
+ end
837
+ end
838
+
839
+ end
840
+
metadata ADDED
@@ -0,0 +1,47 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.0
3
+ specification_version: 1
4
+ name: openwferu-scheduler
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.9.7
7
+ date: 2007-04-01 00:00:00 +09:00
8
+ summary: OpenWFEru scheduler for Ruby (at, cron and every)
9
+ require_paths:
10
+ - lib
11
+ email: john at openwfe dot org
12
+ homepage: http://openwferu.rubyforge.org/scheduler.html
13
+ rubyforge_project:
14
+ description:
15
+ autorequire: openwferu-scheduler
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: false
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - John Mettraux
31
+ files:
32
+ - lib/openwfe/util/otime.rb
33
+ - lib/openwfe/util/scheduler.rb
34
+ test_files: []
35
+
36
+ rdoc_options: []
37
+
38
+ extra_rdoc_files: []
39
+
40
+ executables: []
41
+
42
+ extensions: []
43
+
44
+ requirements: []
45
+
46
+ dependencies: []
47
+