openwferu-scheduler 0.9.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+