rufus-scheduler 2.0.24 → 3.0.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.txt +6 -0
- data/CREDITS.txt +4 -0
- data/README.md +1064 -0
- data/Rakefile +1 -4
- data/TODO.txt +145 -55
- data/lib/rufus/scheduler.rb +502 -26
- data/lib/rufus/{sc → scheduler}/cronline.rb +46 -17
- data/lib/rufus/{sc/version.rb → scheduler/job_array.rb} +56 -4
- data/lib/rufus/scheduler/jobs.rb +548 -0
- data/lib/rufus/scheduler/util.rb +318 -0
- data/rufus-scheduler.gemspec +30 -4
- data/spec/cronline_spec.rb +29 -8
- data/spec/error_spec.rb +116 -0
- data/spec/job_array_spec.rb +39 -0
- data/spec/job_at_spec.rb +58 -0
- data/spec/job_cron_spec.rb +67 -0
- data/spec/job_every_spec.rb +71 -0
- data/spec/job_in_spec.rb +20 -0
- data/spec/job_interval_spec.rb +68 -0
- data/spec/job_repeat_spec.rb +308 -0
- data/spec/job_spec.rb +387 -115
- data/spec/lockfile_spec.rb +61 -0
- data/spec/parse_spec.rb +203 -0
- data/spec/schedule_at_spec.rb +129 -0
- data/spec/schedule_cron_spec.rb +66 -0
- data/spec/schedule_every_spec.rb +109 -0
- data/spec/schedule_in_spec.rb +80 -0
- data/spec/schedule_interval_spec.rb +128 -0
- data/spec/scheduler_spec.rb +831 -124
- data/spec/spec_helper.rb +65 -0
- data/spec/threads_spec.rb +75 -0
- metadata +64 -59
- data/README.rdoc +0 -661
- data/lib/rufus/otime.rb +0 -3
- data/lib/rufus/sc/jobqueues.rb +0 -160
- data/lib/rufus/sc/jobs.rb +0 -471
- data/lib/rufus/sc/rtime.rb +0 -363
- data/lib/rufus/sc/scheduler.rb +0 -636
- data/spec/at_in_spec.rb +0 -47
- data/spec/at_spec.rb +0 -125
- data/spec/blocking_spec.rb +0 -64
- data/spec/cron_spec.rb +0 -134
- data/spec/every_spec.rb +0 -304
- data/spec/exception_spec.rb +0 -113
- data/spec/in_spec.rb +0 -150
- data/spec/mutex_spec.rb +0 -159
- data/spec/rtime_spec.rb +0 -137
- data/spec/schedulable_spec.rb +0 -97
- data/spec/spec_base.rb +0 -87
- data/spec/stress_schedule_unschedule_spec.rb +0 -159
- data/spec/timeout_spec.rb +0 -148
- data/test/kjw.rb +0 -113
- data/test/t.rb +0 -20
@@ -22,10 +22,8 @@
|
|
22
22
|
# Made in Japan.
|
23
23
|
#++
|
24
24
|
|
25
|
-
require 'tzinfo'
|
26
25
|
|
27
|
-
|
28
|
-
module Rufus
|
26
|
+
class Rufus::Scheduler
|
29
27
|
|
30
28
|
#
|
31
29
|
# A 'cron line' is a line in the sense of a crontab
|
@@ -33,9 +31,6 @@ module Rufus
|
|
33
31
|
#
|
34
32
|
class CronLine
|
35
33
|
|
36
|
-
DAY_S = 24 * 3600
|
37
|
-
WEEK_S = 7 * DAY_S
|
38
|
-
|
39
34
|
# The string used for creating this cronline instance.
|
40
35
|
#
|
41
36
|
attr_reader :original
|
@@ -51,7 +46,9 @@ module Rufus
|
|
51
46
|
|
52
47
|
def initialize(line)
|
53
48
|
|
54
|
-
|
49
|
+
raise ArgumentError.new(
|
50
|
+
"not a string: #{line.inspect}"
|
51
|
+
) unless line.is_a?(String)
|
55
52
|
|
56
53
|
@original = line
|
57
54
|
|
@@ -123,12 +120,15 @@ module Rufus
|
|
123
120
|
#
|
124
121
|
# (Thanks to K Liu for the note and the examples)
|
125
122
|
#
|
126
|
-
def next_time(
|
123
|
+
def next_time(from=Time.now)
|
124
|
+
|
125
|
+
time = @timezone ? @timezone.utc_to_local(from.getutc) : from
|
127
126
|
|
128
|
-
time =
|
127
|
+
time = time.respond_to?(:round) ? time.round : time - time.usec * 1e-6
|
128
|
+
# chop off subseconds (and yes, Ruby 1.8 doesn't have #round)
|
129
129
|
|
130
|
-
time = time
|
131
|
-
#
|
130
|
+
time = time + 1
|
131
|
+
# start at the next second
|
132
132
|
|
133
133
|
loop do
|
134
134
|
|
@@ -150,7 +150,7 @@ module Rufus
|
|
150
150
|
|
151
151
|
if @timezone
|
152
152
|
time = @timezone.local_to_utc(time)
|
153
|
-
time = time.getlocal unless
|
153
|
+
time = time.getlocal unless from.utc?
|
154
154
|
end
|
155
155
|
|
156
156
|
time
|
@@ -159,19 +159,19 @@ module Rufus
|
|
159
159
|
# Returns the previous the cronline matched. It's like next_time, but
|
160
160
|
# for the past.
|
161
161
|
#
|
162
|
-
def previous_time(
|
162
|
+
def previous_time(from=Time.now)
|
163
163
|
|
164
164
|
# looks back by slices of two hours,
|
165
165
|
#
|
166
166
|
# finds for '* * * * sun', '* * 13 * *' and '0 12 13 * *'
|
167
167
|
# starting 1970, 1, 1 in 1.8 to 2 seconds (says Rspec)
|
168
168
|
|
169
|
-
start = current =
|
169
|
+
start = current = from - 2 * 3600
|
170
170
|
result = nil
|
171
171
|
|
172
172
|
loop do
|
173
173
|
nex = next_time(current)
|
174
|
-
return (result ? result : previous_time(start)) if nex >
|
174
|
+
return (result ? result : previous_time(start)) if nex > from
|
175
175
|
result = current = nex
|
176
176
|
end
|
177
177
|
|
@@ -196,9 +196,38 @@ module Rufus
|
|
196
196
|
]
|
197
197
|
end
|
198
198
|
|
199
|
-
|
199
|
+
# Returns the shortest delta between two potential occurences of the
|
200
|
+
# schedule described by this cronline.
|
201
|
+
#
|
202
|
+
def frequency
|
203
|
+
|
204
|
+
delta = 366 * DAY_S
|
205
|
+
|
206
|
+
t0 = previous_time(Time.local(2000, 1, 1))
|
207
|
+
|
208
|
+
loop do
|
209
|
+
|
210
|
+
break if delta <= 1
|
211
|
+
break if delta <= 60 && @seconds && @seconds.size == 1
|
212
|
+
|
213
|
+
t1 = next_time(t0)
|
214
|
+
d = t1 - t0
|
215
|
+
delta = d if d < delta
|
216
|
+
|
217
|
+
break if @months == nil && t1.month == 2
|
218
|
+
break if t1.year == 2001
|
219
|
+
|
220
|
+
t0 = t1
|
221
|
+
end
|
222
|
+
|
223
|
+
delta
|
224
|
+
end
|
225
|
+
|
226
|
+
protected
|
200
227
|
|
201
228
|
WEEKDAYS = %w[ sun mon tue wed thu fri sat ]
|
229
|
+
DAY_S = 24 * 3600
|
230
|
+
WEEK_S = 7 * DAY_S
|
202
231
|
|
203
232
|
def parse_weekdays(item)
|
204
233
|
|
@@ -281,7 +310,7 @@ module Rufus
|
|
281
310
|
|
282
311
|
raise ArgumentError.new(
|
283
312
|
"#{item.inspect} is not in range #{min}..#{max}"
|
284
|
-
) if sta < min
|
313
|
+
) if sta < min || edn > max
|
285
314
|
|
286
315
|
r = []
|
287
316
|
val = sta
|
@@ -22,11 +22,63 @@
|
|
22
22
|
# Made in Japan.
|
23
23
|
#++
|
24
24
|
|
25
|
-
|
26
25
|
module Rufus
|
27
|
-
module Scheduler
|
28
26
|
|
29
|
-
|
30
|
-
|
27
|
+
class Scheduler
|
28
|
+
|
29
|
+
#
|
30
|
+
# The array rufus-scheduler uses to keep jobs in order (next to trigger
|
31
|
+
# first).
|
32
|
+
#
|
33
|
+
class JobArray
|
34
|
+
|
35
|
+
def initialize
|
36
|
+
|
37
|
+
@mutex = Mutex.new
|
38
|
+
@array = []
|
39
|
+
end
|
40
|
+
|
41
|
+
def push(job)
|
42
|
+
|
43
|
+
@mutex.synchronize { @array << job unless @array.index(job) }
|
44
|
+
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
def size
|
49
|
+
|
50
|
+
@array.size
|
51
|
+
end
|
52
|
+
|
53
|
+
def each(now, &block)
|
54
|
+
|
55
|
+
to_a.sort_by { |j| j.next_time || (now + 1) }.each do |job|
|
56
|
+
|
57
|
+
break unless job.next_time
|
58
|
+
break if job.next_time > now
|
59
|
+
|
60
|
+
block.call(job)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def delete_unscheduled
|
65
|
+
|
66
|
+
@mutex.synchronize {
|
67
|
+
|
68
|
+
@array.delete_if { |j| j.next_time.nil? || j.unscheduled_at }
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
def to_a
|
73
|
+
|
74
|
+
@mutex.synchronize { @array.dup }
|
75
|
+
end
|
76
|
+
|
77
|
+
def [](job_id)
|
78
|
+
|
79
|
+
@mutex.synchronize { @array.find { |j| j.job_id == job_id } }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
31
83
|
end
|
32
84
|
|
@@ -0,0 +1,548 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2006-2013, John Mettraux, jmettraux@gmail.com
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
9
|
+
# furnished to do so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
12
|
+
# all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
# THE SOFTWARE.
|
21
|
+
#
|
22
|
+
# Made in Japan.
|
23
|
+
#++
|
24
|
+
|
25
|
+
|
26
|
+
module Rufus
|
27
|
+
|
28
|
+
class Scheduler
|
29
|
+
|
30
|
+
#--
|
31
|
+
# job classes
|
32
|
+
#++
|
33
|
+
|
34
|
+
class Job
|
35
|
+
|
36
|
+
#
|
37
|
+
# Used by Job#kill
|
38
|
+
#
|
39
|
+
class KillSignal < StandardError; end
|
40
|
+
|
41
|
+
attr_reader :id
|
42
|
+
attr_reader :opts
|
43
|
+
attr_reader :original
|
44
|
+
attr_reader :scheduled_at
|
45
|
+
attr_reader :last_time
|
46
|
+
attr_reader :unscheduled_at
|
47
|
+
attr_reader :tags
|
48
|
+
|
49
|
+
# next trigger time
|
50
|
+
#
|
51
|
+
attr_accessor :next_time
|
52
|
+
|
53
|
+
# anything with a #call(job[, timet]) method,
|
54
|
+
# what gets actually triggered
|
55
|
+
#
|
56
|
+
attr_reader :callable
|
57
|
+
|
58
|
+
# a reference to the instance whose call method is the @callable
|
59
|
+
#
|
60
|
+
attr_reader :handler
|
61
|
+
|
62
|
+
def initialize(scheduler, original, opts, block)
|
63
|
+
|
64
|
+
@scheduler = scheduler
|
65
|
+
@original = original
|
66
|
+
@opts = opts
|
67
|
+
|
68
|
+
@handler = block
|
69
|
+
|
70
|
+
@callable =
|
71
|
+
if block.respond_to?(:arity)
|
72
|
+
block
|
73
|
+
elsif block.respond_to?(:call)
|
74
|
+
block.method(:call)
|
75
|
+
elsif block.is_a?(Class)
|
76
|
+
@handler = block.new
|
77
|
+
@handler.method(:call) rescue nil
|
78
|
+
else
|
79
|
+
nil
|
80
|
+
end
|
81
|
+
|
82
|
+
@scheduled_at = Time.now
|
83
|
+
@unscheduled_at = nil
|
84
|
+
@last_time = nil
|
85
|
+
#@mutexes = {}
|
86
|
+
#@pool_mutex = Mutex.new
|
87
|
+
|
88
|
+
@locals = {}
|
89
|
+
@local_mutex = Mutex.new
|
90
|
+
|
91
|
+
@id = determine_id
|
92
|
+
|
93
|
+
raise(
|
94
|
+
ArgumentError,
|
95
|
+
'missing block or callable to schedule',
|
96
|
+
caller[2..-1]
|
97
|
+
) unless @callable
|
98
|
+
|
99
|
+
@tags = Array(opts[:tag] || opts[:tags]).collect { |t| t.to_s }
|
100
|
+
|
101
|
+
# tidy up options
|
102
|
+
|
103
|
+
if @opts[:allow_overlap] == false || @opts[:allow_overlapping] == false
|
104
|
+
@opts[:overlap] = false
|
105
|
+
end
|
106
|
+
if m = @opts[:mutex]
|
107
|
+
@opts[:mutex] = Array(m)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
alias job_id id
|
112
|
+
|
113
|
+
def trigger(time)
|
114
|
+
|
115
|
+
set_next_time(false, time)
|
116
|
+
|
117
|
+
return if opts[:overlap] == false && running?
|
118
|
+
|
119
|
+
r = callback(:pre, time)
|
120
|
+
|
121
|
+
return if r == false
|
122
|
+
|
123
|
+
if opts[:blocking]
|
124
|
+
do_trigger(time)
|
125
|
+
else
|
126
|
+
do_trigger_in_thread(time)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def unschedule
|
131
|
+
|
132
|
+
@unscheduled_at = Time.now
|
133
|
+
end
|
134
|
+
|
135
|
+
def threads
|
136
|
+
|
137
|
+
Thread.list.select { |t| t[:rufus_scheduler_job] == self }
|
138
|
+
end
|
139
|
+
|
140
|
+
# Kills all the threads this Job currently has going on.
|
141
|
+
#
|
142
|
+
def kill
|
143
|
+
|
144
|
+
threads.each { |t| t.raise(KillSignal) }
|
145
|
+
end
|
146
|
+
|
147
|
+
def running?
|
148
|
+
|
149
|
+
threads.any?
|
150
|
+
end
|
151
|
+
|
152
|
+
def scheduled?
|
153
|
+
|
154
|
+
@scheduler.scheduled?(self)
|
155
|
+
end
|
156
|
+
|
157
|
+
def []=(key, value)
|
158
|
+
|
159
|
+
@local_mutex.synchronize { @locals[key] = value }
|
160
|
+
end
|
161
|
+
|
162
|
+
def [](key)
|
163
|
+
|
164
|
+
@local_mutex.synchronize { @locals[key] }
|
165
|
+
end
|
166
|
+
|
167
|
+
def key?(key)
|
168
|
+
|
169
|
+
@local_mutex.synchronize { @locals.key?(key) }
|
170
|
+
end
|
171
|
+
|
172
|
+
def keys
|
173
|
+
|
174
|
+
@local_mutex.synchronize { @locals.keys }
|
175
|
+
end
|
176
|
+
|
177
|
+
#def hash
|
178
|
+
# self.object_id
|
179
|
+
#end
|
180
|
+
#def eql?(o)
|
181
|
+
# o.class == self.class && o.hash == self.hash
|
182
|
+
#end
|
183
|
+
#
|
184
|
+
# might be necessary at some point
|
185
|
+
|
186
|
+
protected
|
187
|
+
|
188
|
+
def callback(position, time)
|
189
|
+
|
190
|
+
name = position == :pre ? :on_pre_trigger : :on_post_trigger
|
191
|
+
|
192
|
+
return unless @scheduler.respond_to?(name)
|
193
|
+
|
194
|
+
args = @scheduler.method(name).arity < 2 ? [ self ] : [ self, time ]
|
195
|
+
|
196
|
+
@scheduler.send(name, *args)
|
197
|
+
end
|
198
|
+
|
199
|
+
def compute_timeout
|
200
|
+
|
201
|
+
if to = @opts[:timeout]
|
202
|
+
Rufus::Scheduler.parse(to)
|
203
|
+
else
|
204
|
+
nil
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def mutex(m)
|
209
|
+
|
210
|
+
m.is_a?(Mutex) ? m : (@scheduler.mutexes[m.to_s] ||= Mutex.new)
|
211
|
+
end
|
212
|
+
|
213
|
+
def do_trigger(time)
|
214
|
+
|
215
|
+
t = Time.now
|
216
|
+
# if there are mutexes, t might be really bigger than time
|
217
|
+
|
218
|
+
Thread.current[:rufus_scheduler_job] = self
|
219
|
+
Thread.current[:rufus_scheduler_time] = t
|
220
|
+
Thread.current[:rufus_scheduler_timeout] = compute_timeout
|
221
|
+
|
222
|
+
@last_time = t
|
223
|
+
|
224
|
+
args = [ self, time ][0, @callable.arity]
|
225
|
+
@callable.call(*args)
|
226
|
+
|
227
|
+
rescue KillSignal
|
228
|
+
|
229
|
+
# discard
|
230
|
+
|
231
|
+
rescue StandardError => se
|
232
|
+
|
233
|
+
@scheduler.on_error(self, se)
|
234
|
+
|
235
|
+
ensure
|
236
|
+
|
237
|
+
post_trigger(time)
|
238
|
+
|
239
|
+
Thread.current[:rufus_scheduler_job] = nil
|
240
|
+
Thread.current[:rufus_scheduler_time] = nil
|
241
|
+
Thread.current[:rufus_scheduler_timeout] = nil
|
242
|
+
end
|
243
|
+
|
244
|
+
def post_trigger(time)
|
245
|
+
|
246
|
+
set_next_time(true, time)
|
247
|
+
|
248
|
+
callback(:post, time)
|
249
|
+
end
|
250
|
+
|
251
|
+
def start_work_thread
|
252
|
+
|
253
|
+
thread =
|
254
|
+
Thread.new do
|
255
|
+
|
256
|
+
Thread.current[@scheduler.thread_key] = true
|
257
|
+
Thread.current[:rufus_scheduler_job_thread] = true
|
258
|
+
|
259
|
+
loop do
|
260
|
+
|
261
|
+
job, time = @scheduler.work_queue.pop
|
262
|
+
|
263
|
+
break if @scheduler.started_at == nil
|
264
|
+
|
265
|
+
next if job.unscheduled_at
|
266
|
+
|
267
|
+
begin
|
268
|
+
|
269
|
+
(job.opts[:mutex] || []).reduce(
|
270
|
+
lambda { job.do_trigger(time) }
|
271
|
+
) do |b, m|
|
272
|
+
lambda { mutex(m).synchronize { b.call } }
|
273
|
+
end.call
|
274
|
+
|
275
|
+
rescue KillSignal
|
276
|
+
|
277
|
+
# simply go on looping
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
thread[@scheduler.thread_key] = true
|
283
|
+
thread[:rufus_scheduler_work_thread] = true
|
284
|
+
#
|
285
|
+
# same as above (in the thead block),
|
286
|
+
# but since it has to be done as quickly as possible.
|
287
|
+
# So, whoever is running first (scheduler thread vs job thread)
|
288
|
+
# sets this information
|
289
|
+
end
|
290
|
+
|
291
|
+
def do_trigger_in_thread(time)
|
292
|
+
|
293
|
+
#@pool_mutex.synchronize do
|
294
|
+
|
295
|
+
count = @scheduler.work_threads.size
|
296
|
+
#vacant = threads.select { |t| t[:rufus_scheduler_job] == nil }.size
|
297
|
+
#min = @scheduler.min_work_threads
|
298
|
+
max = @scheduler.max_work_threads
|
299
|
+
|
300
|
+
start_work_thread if count < max
|
301
|
+
#end
|
302
|
+
|
303
|
+
@scheduler.work_queue << [ self, time ]
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
class OneTimeJob < Job
|
308
|
+
|
309
|
+
alias time next_time
|
310
|
+
|
311
|
+
protected
|
312
|
+
|
313
|
+
def determine_id
|
314
|
+
|
315
|
+
[
|
316
|
+
self.class.name.split(':').last.downcase[0..-4],
|
317
|
+
@scheduled_at.to_f,
|
318
|
+
@next_time.to_f,
|
319
|
+
opts.hash.abs
|
320
|
+
].map(&:to_s).join('_')
|
321
|
+
end
|
322
|
+
|
323
|
+
# There is no next_time for one time jobs, hence the false.
|
324
|
+
#
|
325
|
+
def set_next_time(is_post, trigger_time)
|
326
|
+
|
327
|
+
@next_time = is_post ? nil : false
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
class AtJob < OneTimeJob
|
332
|
+
|
333
|
+
def initialize(scheduler, time, opts, block)
|
334
|
+
|
335
|
+
super(scheduler, time, opts, block)
|
336
|
+
|
337
|
+
@next_time = Rufus::Scheduler.parse_at(time)
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
class InJob < OneTimeJob
|
342
|
+
|
343
|
+
def initialize(scheduler, duration, opts, block)
|
344
|
+
|
345
|
+
super(scheduler, duration, opts, block)
|
346
|
+
|
347
|
+
@next_time = @scheduled_at + Rufus::Scheduler.parse_in(duration)
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
class RepeatJob < Job
|
352
|
+
|
353
|
+
attr_reader :paused_at
|
354
|
+
|
355
|
+
attr_reader :first_at
|
356
|
+
attr_accessor :last_at
|
357
|
+
attr_accessor :times
|
358
|
+
|
359
|
+
def initialize(scheduler, duration, opts, block)
|
360
|
+
|
361
|
+
super
|
362
|
+
|
363
|
+
@paused_at = nil
|
364
|
+
|
365
|
+
@times = opts[:times]
|
366
|
+
|
367
|
+
raise ArgumentError.new(
|
368
|
+
"cannot accept :times => #{@times.inspect}, not nil or an int"
|
369
|
+
) unless @times == nil || @times.is_a?(Fixnum)
|
370
|
+
|
371
|
+
self.first_at =
|
372
|
+
opts[:first] || opts[:first_at] || opts[:first_in] || 0
|
373
|
+
self.last_at =
|
374
|
+
opts[:last] || opts[:last_at] || opts[:last_in]
|
375
|
+
end
|
376
|
+
|
377
|
+
def first_at=(first)
|
378
|
+
|
379
|
+
@first_at = Rufus::Scheduler.parse_to_time(first)
|
380
|
+
|
381
|
+
raise ArgumentError.new(
|
382
|
+
"cannot set first[_at|_in] in the past: " +
|
383
|
+
"#{first.inspect} -> #{@first_at.inspect}"
|
384
|
+
) if first != 0 && @first_at < Time.now
|
385
|
+
end
|
386
|
+
|
387
|
+
def last_at=(last)
|
388
|
+
|
389
|
+
@last_at = last ? Rufus::Scheduler.parse_to_time(last) : nil
|
390
|
+
|
391
|
+
raise ArgumentError.new(
|
392
|
+
"cannot set last[_at|_in] in the past: " +
|
393
|
+
"#{last.inspect} -> #{@last_at.inspect}"
|
394
|
+
) if last && @last_at < Time.now
|
395
|
+
end
|
396
|
+
|
397
|
+
def trigger(time)
|
398
|
+
|
399
|
+
return if @paused_at
|
400
|
+
return if time < @first_at
|
401
|
+
#
|
402
|
+
# TODO: remove me when @first_at gets reworked
|
403
|
+
|
404
|
+
return (@next_time = nil) if @times && @times < 1
|
405
|
+
return (@next_time = nil) if @last_at && time >= @last_at
|
406
|
+
#
|
407
|
+
# TODO: rework that, jobs are thus kept 1 step too much in @jobs
|
408
|
+
|
409
|
+
super
|
410
|
+
|
411
|
+
@times = @times - 1 if @times
|
412
|
+
end
|
413
|
+
|
414
|
+
def pause
|
415
|
+
|
416
|
+
@paused_at = Time.now
|
417
|
+
end
|
418
|
+
|
419
|
+
def resume
|
420
|
+
|
421
|
+
@paused_at = nil
|
422
|
+
end
|
423
|
+
|
424
|
+
def paused?
|
425
|
+
|
426
|
+
@paused_at != nil
|
427
|
+
end
|
428
|
+
|
429
|
+
def determine_id
|
430
|
+
|
431
|
+
[
|
432
|
+
self.class.name.split(':').last.downcase[0..-4],
|
433
|
+
@scheduled_at.to_f,
|
434
|
+
opts.hash.abs
|
435
|
+
].map(&:to_s).join('_')
|
436
|
+
end
|
437
|
+
end
|
438
|
+
|
439
|
+
#
|
440
|
+
# A parent class of EveryJob and IntervalJob
|
441
|
+
#
|
442
|
+
class EvInJob < RepeatJob
|
443
|
+
|
444
|
+
def first_at=(first)
|
445
|
+
|
446
|
+
super
|
447
|
+
|
448
|
+
@next_time = @first_at
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
class EveryJob < EvInJob
|
453
|
+
|
454
|
+
attr_reader :frequency
|
455
|
+
|
456
|
+
def initialize(scheduler, duration, opts, block)
|
457
|
+
|
458
|
+
super(scheduler, duration, opts, block)
|
459
|
+
|
460
|
+
@frequency = Rufus::Scheduler.parse_in(@original)
|
461
|
+
|
462
|
+
raise ArgumentError.new(
|
463
|
+
"cannot schedule #{self.class} with a frequency " +
|
464
|
+
"of #{@frequency.inspect} (#{@original.inspect})"
|
465
|
+
) if @frequency <= 0
|
466
|
+
|
467
|
+
set_next_time(false, nil)
|
468
|
+
end
|
469
|
+
|
470
|
+
protected
|
471
|
+
|
472
|
+
def set_next_time(is_post, trigger_time)
|
473
|
+
|
474
|
+
return if is_post
|
475
|
+
|
476
|
+
@next_time =
|
477
|
+
if trigger_time
|
478
|
+
trigger_time + @frequency
|
479
|
+
elsif @first_at < Time.now
|
480
|
+
Time.now + @frequency
|
481
|
+
else
|
482
|
+
@first_at
|
483
|
+
end
|
484
|
+
end
|
485
|
+
end
|
486
|
+
|
487
|
+
class IntervalJob < EvInJob
|
488
|
+
|
489
|
+
attr_reader :interval
|
490
|
+
|
491
|
+
def initialize(scheduler, interval, opts, block)
|
492
|
+
|
493
|
+
super(scheduler, interval, opts, block)
|
494
|
+
|
495
|
+
@interval = Rufus::Scheduler.parse_in(@original)
|
496
|
+
|
497
|
+
raise ArgumentError.new(
|
498
|
+
"cannot schedule #{self.class} with an interval " +
|
499
|
+
"of #{@interval.inspect} (#{@original.inspect})"
|
500
|
+
) if @interval <= 0
|
501
|
+
|
502
|
+
set_next_time(false, nil)
|
503
|
+
end
|
504
|
+
|
505
|
+
protected
|
506
|
+
|
507
|
+
def set_next_time(is_post, trigger_time)
|
508
|
+
|
509
|
+
@next_time =
|
510
|
+
if is_post
|
511
|
+
Time.now + @interval
|
512
|
+
elsif trigger_time.nil?
|
513
|
+
if @first_at < Time.now
|
514
|
+
Time.now + @interval
|
515
|
+
else
|
516
|
+
@first_at
|
517
|
+
end
|
518
|
+
else
|
519
|
+
false
|
520
|
+
end
|
521
|
+
end
|
522
|
+
end
|
523
|
+
|
524
|
+
class CronJob < RepeatJob
|
525
|
+
|
526
|
+
def initialize(scheduler, cronline, opts, block)
|
527
|
+
|
528
|
+
super(scheduler, cronline, opts, block)
|
529
|
+
|
530
|
+
@cron_line = CronLine.new(cronline)
|
531
|
+
@next_time = @cron_line.next_time
|
532
|
+
end
|
533
|
+
|
534
|
+
def frequency
|
535
|
+
|
536
|
+
@cron_line.frequency
|
537
|
+
end
|
538
|
+
|
539
|
+
protected
|
540
|
+
|
541
|
+
def set_next_time(is_post, trigger_time)
|
542
|
+
|
543
|
+
@next_time = @cron_line.next_time
|
544
|
+
end
|
545
|
+
end
|
546
|
+
end
|
547
|
+
end
|
548
|
+
|