rufus-scheduler 3.1.4 → 3.8.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +410 -0
  3. data/CREDITS.md +142 -0
  4. data/LICENSE.txt +1 -1
  5. data/Makefile +27 -0
  6. data/README.md +407 -143
  7. data/lib/rufus/scheduler/job_array.rb +36 -66
  8. data/lib/rufus/scheduler/jobs_core.rb +369 -0
  9. data/lib/rufus/scheduler/jobs_one_time.rb +53 -0
  10. data/lib/rufus/scheduler/jobs_repeat.rb +335 -0
  11. data/lib/rufus/scheduler/locks.rb +41 -67
  12. data/lib/rufus/scheduler/util.rb +89 -179
  13. data/lib/rufus/scheduler.rb +545 -453
  14. data/rufus-scheduler.gemspec +22 -11
  15. metadata +44 -85
  16. data/CHANGELOG.txt +0 -243
  17. data/CREDITS.txt +0 -88
  18. data/Rakefile +0 -83
  19. data/TODO.txt +0 -151
  20. data/lib/rufus/scheduler/cronline.rb +0 -470
  21. data/lib/rufus/scheduler/jobs.rb +0 -633
  22. data/lib/rufus/scheduler/zones.rb +0 -174
  23. data/lib/rufus/scheduler/zotime.rb +0 -155
  24. data/spec/basics_spec.rb +0 -54
  25. data/spec/cronline_spec.rb +0 -915
  26. data/spec/error_spec.rb +0 -139
  27. data/spec/job_array_spec.rb +0 -39
  28. data/spec/job_at_spec.rb +0 -58
  29. data/spec/job_cron_spec.rb +0 -128
  30. data/spec/job_every_spec.rb +0 -104
  31. data/spec/job_in_spec.rb +0 -20
  32. data/spec/job_interval_spec.rb +0 -68
  33. data/spec/job_repeat_spec.rb +0 -357
  34. data/spec/job_spec.rb +0 -631
  35. data/spec/lock_custom_spec.rb +0 -47
  36. data/spec/lock_flock_spec.rb +0 -47
  37. data/spec/lock_lockfile_spec.rb +0 -61
  38. data/spec/lock_spec.rb +0 -59
  39. data/spec/parse_spec.rb +0 -263
  40. data/spec/schedule_at_spec.rb +0 -158
  41. data/spec/schedule_cron_spec.rb +0 -66
  42. data/spec/schedule_every_spec.rb +0 -109
  43. data/spec/schedule_in_spec.rb +0 -80
  44. data/spec/schedule_interval_spec.rb +0 -128
  45. data/spec/scheduler_spec.rb +0 -1067
  46. data/spec/spec_helper.rb +0 -126
  47. data/spec/threads_spec.rb +0 -96
  48. data/spec/zotime_spec.rb +0 -396
@@ -0,0 +1,335 @@
1
+
2
+ class Rufus::Scheduler::RepeatJob < Rufus::Scheduler::Job
3
+
4
+ attr_reader :paused_at
5
+
6
+ attr_reader :first_at
7
+ attr_reader :last_at
8
+ attr_accessor :times
9
+
10
+ def initialize(scheduler, duration, opts, block)
11
+
12
+ super
13
+
14
+ @paused_at = nil
15
+
16
+ @times = opts[:times]
17
+
18
+ fail ArgumentError.new(
19
+ "cannot accept :times => #{@times.inspect}, not nil or an int"
20
+ ) unless @times == nil || @times.is_a?(Integer)
21
+
22
+ self.first_at =
23
+ opts[:first] || opts[:first_time] ||
24
+ opts[:first_at] || opts[:first_in] ||
25
+ nil
26
+ self.last_at =
27
+ opts[:last] || opts[:last_at] || opts[:last_in]
28
+
29
+ @resume_discard_past = nil
30
+ end
31
+
32
+ FIRSTS = [ :now, :immediately, 0 ].freeze
33
+
34
+ def first_at=(first)
35
+
36
+ return (@first_at = nil) if first == nil
37
+
38
+ n0 = EoTime.now
39
+ n1 = n0 + 0.003
40
+
41
+ first = n0 if FIRSTS.include?(first)
42
+ fdur = Rufus::Scheduler.parse_duration(first, no_error: true)
43
+
44
+ @first_at = (fdur && (EoTime.now + fdur)) || EoTime.make(first)
45
+ @first_at = n1 if @first_at >= n0 && @first_at < n1
46
+
47
+ fail ArgumentError.new(
48
+ "cannot set first[_at|_in] in the past: " +
49
+ "#{first.inspect} -> #{@first_at.inspect}"
50
+ ) if @first_at < n0
51
+
52
+ @first_at
53
+ end
54
+
55
+ def last_at=(last)
56
+
57
+ @last_at =
58
+ if last
59
+ ldur = Rufus::Scheduler.parse_duration(last, no_error: true)
60
+ (ldur && (EoTime.now + ldur)) || EoTime.make(last)
61
+ else
62
+ nil
63
+ end
64
+
65
+ fail ArgumentError.new(
66
+ "cannot set last[_at|_in] in the past: " +
67
+ "#{last.inspect} -> #{@last_at.inspect}"
68
+ ) if last && @last_at < EoTime.now
69
+
70
+ @last_at
71
+ end
72
+
73
+ def trigger(time)
74
+
75
+ return if @paused_at
76
+ #return set_next_time(time) if @paused_at
77
+
78
+ return (@next_time = nil) if @times && @times < 1
79
+ return (@next_time = nil) if @last_at && time >= @last_at
80
+ #
81
+ # It keeps jobs one step too much in @jobs, but it's OK
82
+
83
+ super
84
+
85
+ @times -= 1 if @times
86
+ end
87
+
88
+ def pause
89
+
90
+ @paused_at = EoTime.now
91
+ end
92
+
93
+ def resume(opts={})
94
+
95
+ @resume_discard_past = opts[:discard_past]
96
+ #p [ :@resume_discard_past, @resume_discard_past ]
97
+ @paused_at = nil
98
+ end
99
+
100
+ def paused?
101
+
102
+ !! @paused_at
103
+ end
104
+
105
+ def determine_id
106
+
107
+ [
108
+ self.class.name.split(':').last.downcase[0..-4],
109
+ @scheduled_at.to_f,
110
+ (self.object_id < 0 ? 'm' : '') + self.object_id.to_s
111
+ ].map(&:to_s).join('_')
112
+ end
113
+
114
+ def occurrences(time0, time1)
115
+
116
+ a = []
117
+
118
+ nt = @next_time
119
+ ts = @times
120
+
121
+ loop do
122
+
123
+ break if nt > time1
124
+ break if ts && ts <= 0
125
+
126
+ a << nt if nt >= time0
127
+
128
+ nt = next_time_from(nt)
129
+ ts = ts - 1 if ts
130
+ end
131
+
132
+ a
133
+ end
134
+
135
+ # Starting from now, returns the {count} next occurences
136
+ # (EtOrbi::EoTime instances) for this job.
137
+ #
138
+ # Warning, for IntervalJob, the @mean_work_time is used since
139
+ # "interval" works from the end of a job to its next trigger
140
+ # (not from one trigger to the next, as for "cron" and "every").
141
+ #
142
+ def next_times(count)
143
+
144
+ (count - 1).times.inject([ next_time ]) { |a|
145
+ a << next_time_from(a.last)
146
+ a }
147
+ end
148
+
149
+ protected
150
+
151
+ def discard_past?
152
+
153
+ dp = @scheduler.discard_past
154
+ dp = @discard_past if @discard_past != nil
155
+ dp = @resume_discard_past if @resume_discard_past != nil
156
+
157
+ dp
158
+ end
159
+ end
160
+
161
+ #
162
+ # A parent class of EveryJob and IntervalJob
163
+ #
164
+ class Rufus::Scheduler::EvInJob < Rufus::Scheduler::RepeatJob
165
+
166
+ def first_at=(first)
167
+
168
+ @next_time = super
169
+ end
170
+ end
171
+
172
+ class Rufus::Scheduler::EveryJob < Rufus::Scheduler::EvInJob
173
+
174
+ attr_reader :frequency
175
+
176
+ attr_accessor :resume_discard_past
177
+
178
+ def initialize(scheduler, duration, opts, block)
179
+
180
+ super(scheduler, duration, opts, block)
181
+
182
+ @frequency = Rufus::Scheduler.parse_in(@original)
183
+ @discard_past = opts[:discard_past]
184
+
185
+ fail ArgumentError.new(
186
+ "cannot schedule #{self.class} with a frequency " +
187
+ "of #{@frequency.inspect} (#{@original.inspect})"
188
+ ) if @frequency <= 0
189
+
190
+ set_next_time(nil)
191
+ end
192
+
193
+ def check_frequency
194
+
195
+ fail ArgumentError.new(
196
+ "job frequency (#{@frequency}s) is higher than " +
197
+ "scheduler frequency (#{@scheduler.frequency}s)"
198
+ ) if @frequency < @scheduler.frequency
199
+ end
200
+
201
+ def next_time_from(time)
202
+
203
+ time + @frequency
204
+ end
205
+
206
+ protected
207
+
208
+ def set_next_time(trigger_time, is_post=false, now=nil)
209
+
210
+ return if is_post
211
+
212
+ n = now || EoTime.now
213
+
214
+ return @next_time = @first_at \
215
+ if @first_at && (trigger_time == nil || @first_at > n)
216
+
217
+ dp = discard_past?
218
+
219
+ loop do
220
+
221
+ @next_time = (@next_time || n) + @frequency
222
+
223
+ break if dp == false
224
+ break if @next_time > n
225
+ end
226
+ end
227
+ end
228
+
229
+ class Rufus::Scheduler::IntervalJob < Rufus::Scheduler::EvInJob
230
+
231
+ attr_reader :interval
232
+
233
+ def initialize(scheduler, interval, opts, block)
234
+
235
+ super(scheduler, interval, opts, block)
236
+
237
+ @interval = Rufus::Scheduler.parse_in(@original)
238
+
239
+ fail ArgumentError.new(
240
+ "cannot schedule #{self.class} with an interval " +
241
+ "of #{@interval.inspect} (#{@original.inspect})"
242
+ ) if @interval <= 0
243
+
244
+ set_next_time(nil)
245
+ end
246
+
247
+ def next_time_from(time)
248
+
249
+ time + @mean_work_time + @interval
250
+ end
251
+
252
+ protected
253
+
254
+ def set_next_time(trigger_time, is_post=false, now=nil)
255
+
256
+ n = now || EoTime.now
257
+
258
+ @next_time =
259
+ if is_post
260
+ n + @interval
261
+ elsif trigger_time.nil?
262
+ if @first_at == nil || @first_at < n
263
+ n + @interval
264
+ else
265
+ @first_at
266
+ end
267
+ else
268
+ false
269
+ end
270
+ end
271
+ end
272
+
273
+ class Rufus::Scheduler::CronJob < Rufus::Scheduler::RepeatJob
274
+
275
+ attr_reader :cron_line
276
+
277
+ def initialize(scheduler, cronline, opts, block)
278
+
279
+ super(scheduler, cronline, opts, block)
280
+
281
+ @cron_line = opts[:_t] || ::Fugit::Cron.do_parse(cronline)
282
+
283
+ set_next_time(nil)
284
+ end
285
+
286
+ def check_frequency
287
+
288
+ return if @scheduler.frequency <= 1
289
+ #
290
+ # The minimum time delta in a cron job is 1 second, so if the
291
+ # scheduler frequency is less than that, no worries.
292
+
293
+ f = @cron_line.rough_frequency
294
+
295
+ fail ArgumentError.new(
296
+ "job frequency (min ~#{f}s) is higher than " +
297
+ "scheduler frequency (#{@scheduler.frequency}s)"
298
+ ) if f < @scheduler.frequency
299
+ end
300
+
301
+ def brute_frequency
302
+
303
+ @cron_line.brute_frequency
304
+ end
305
+
306
+ def rough_frequency
307
+
308
+ @cron_line.rough_frequency
309
+ end
310
+
311
+ def next_time_from(time)
312
+
313
+ @cron_line.next_time(time)
314
+ end
315
+
316
+ protected
317
+
318
+ def set_next_time(trigger_time, is_post=false, now=nil)
319
+
320
+ return if is_post
321
+
322
+ t = trigger_time || now || EoTime.now
323
+
324
+ previous = @previous_time || @scheduled_at
325
+ t = previous if ! discard_past? && t > previous
326
+
327
+ @next_time =
328
+ if @first_at && @first_at > t
329
+ @first_at
330
+ else
331
+ @cron_line.next_time(t)
332
+ end
333
+ end
334
+ end
335
+
@@ -1,95 +1,69 @@
1
- #--
2
- # Copyright (c) 2006-2015, 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
1
 
25
2
  require 'fileutils'
26
3
 
27
4
 
28
- class Rufus::Scheduler
5
+ #
6
+ # A lock that can always be acquired
7
+ #
8
+ class Rufus::Scheduler::NullLock
29
9
 
10
+ # Locking is always successful.
30
11
  #
31
- # A lock that can always be acquired
32
- #
33
- class NullLock
12
+ def lock; true; end
34
13
 
35
- # Locking is always successful.
36
- #
37
- def lock; true; end
38
-
39
- def locked?; true; end
40
- def unlock; true; end
41
- end
14
+ def locked?; true; end
15
+ def unlock; true; end
16
+ end
42
17
 
43
- #
44
- # The standard flock mecha, with its own class thanks to @ecin
45
- #
46
- class FileLock
18
+ #
19
+ # The standard flock mechanism, with its own class thanks to @ecin
20
+ #
21
+ class Rufus::Scheduler::FileLock
47
22
 
48
- attr_reader :path
23
+ attr_reader :path
49
24
 
50
- def initialize(path)
25
+ def initialize(path)
51
26
 
52
- @path = path.to_s
53
- end
27
+ @path = path.to_s
28
+ end
54
29
 
55
- # Locking is successful if this Ruby process can create and lock
56
- # its lockfile (at the given path).
57
- #
58
- def lock
30
+ # Locking is successful if this Ruby process can create and lock
31
+ # its lockfile (at the given path).
32
+ #
33
+ def lock
59
34
 
60
- return true if locked?
35
+ return true if locked?
61
36
 
62
- @lockfile = nil
37
+ @lockfile = nil
63
38
 
64
- FileUtils.mkdir_p(::File.dirname(@path))
39
+ FileUtils.mkdir_p(::File.dirname(@path))
65
40
 
66
- file = File.new(@path, File::RDWR | File::CREAT)
67
- locked = file.flock(File::LOCK_NB | File::LOCK_EX)
41
+ file = File.new(@path, File::RDWR | File::CREAT)
42
+ locked = file.flock(File::LOCK_NB | File::LOCK_EX)
68
43
 
69
- return false unless locked
44
+ return false unless locked
70
45
 
71
- now = Time.now
46
+ now = Time.now
72
47
 
73
- file.print("pid: #{$$}, ")
74
- file.print("scheduler.object_id: #{self.object_id}, ")
75
- file.print("time: #{now}, ")
76
- file.print("timestamp: #{now.to_f}")
77
- file.flush
48
+ file.print("pid: #{$$}, ")
49
+ file.print("scheduler.object_id: #{self.object_id}, ")
50
+ file.print("time: #{now}, ")
51
+ file.print("timestamp: #{now.to_f}")
52
+ file.flush
78
53
 
79
- @lockfile = file
54
+ @lockfile = file
80
55
 
81
- true
82
- end
56
+ true
57
+ end
83
58
 
84
- def unlock
59
+ def unlock
85
60
 
86
- !! (@lockfile && @lockfile.flock(File::LOCK_UN))
87
- end
61
+ !! (@lockfile && @lockfile.flock(File::LOCK_UN))
62
+ end
88
63
 
89
- def locked?
64
+ def locked?
90
65
 
91
- !! (@lockfile && @lockfile.flock(File::LOCK_NB | File::LOCK_EX))
92
- end
66
+ !! (@lockfile && @lockfile.flock(File::LOCK_NB | File::LOCK_EX))
93
67
  end
94
68
  end
95
69