tanzeeb-rufus-scheduler 2.0.7.2

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,363 @@
1
+ #--
2
+ # Copyright (c) 2006-2010, 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
+ module Scheduler
28
+
29
+ #
30
+ # The base class for all types of jobs.
31
+ #
32
+ class Job
33
+
34
+ # A reference to the scheduler owning this job
35
+ #
36
+ attr_accessor :scheduler
37
+
38
+ # The initial, raw, scheduling info (at / in / every / cron)
39
+ #
40
+ attr_reader :t
41
+
42
+ # Returns the thread instance of the last triggered job.
43
+ # May be null (especially before the first trigger).
44
+ #
45
+ attr_reader :last_job_thread
46
+
47
+ # The job parameters (passed via the schedule method)
48
+ #
49
+ attr_reader :params
50
+
51
+ # The block to call when triggering
52
+ #
53
+ attr_reader :block
54
+
55
+ # Last time the job executed
56
+ # (for an {At|In}Job, it will mean 'not executed' if nil or when
57
+ # it got executed if set)
58
+ #
59
+ # (
60
+ # Last time job got triggered (most useful with EveryJob, but can be
61
+ # useful with remaining instances of At/InJob (are they done ?))
62
+ # )
63
+ #
64
+ attr_reader :last
65
+
66
+ # The identifier for this job.
67
+ #
68
+ attr_reader :job_id
69
+
70
+ attr_accessor :running
71
+
72
+ # Instantiating the job.
73
+ #
74
+ def initialize (scheduler, t, params, &block)
75
+
76
+ @scheduler = scheduler
77
+ @t = t
78
+ @params = params
79
+ @block = block || params[:schedulable]
80
+
81
+ @running = false
82
+ @allow_overlapping = true
83
+ if !params[:allow_overlapping].nil?
84
+ @allow_overlapping = params[:allow_overlapping]
85
+ end
86
+
87
+ raise ArgumentError.new(
88
+ 'no block or :schedulable passed, nothing to schedule'
89
+ ) unless @block
90
+
91
+ @params[:tags] = Array(@params[:tags])
92
+
93
+ @job_id = params[:job_id] || "#{self.class.name}_#{self.object_id.to_s}"
94
+
95
+ determine_at
96
+ end
97
+
98
+ # Returns the list of tags attached to the job.
99
+ #
100
+ def tags
101
+
102
+ @params[:tags]
103
+ end
104
+
105
+ # Sets the list of tags attached to the job (Usually they are set
106
+ # via the schedule every/at/in/cron method).
107
+ #
108
+ def tags= (tags)
109
+
110
+ @params[:tags] = Array(tags)
111
+ end
112
+
113
+ # Generally returns the string/float/integer used to schedule the job
114
+ # (seconds, time string, date string)
115
+ #
116
+ def schedule_info
117
+
118
+ @t
119
+ end
120
+
121
+ # Triggers the job.
122
+ #
123
+ def trigger (t=Time.now)
124
+
125
+ @last = t
126
+ job_thread = nil
127
+ to_job = nil
128
+
129
+ return if @running && !@allow_overlapping
130
+
131
+ @running = true
132
+ @scheduler.send(:trigger_job, @params[:blocking]) do
133
+ #
134
+ # Note that #trigger_job is protected, hence the #send
135
+ # (Only jobs know about this method of the scheduler)
136
+
137
+ job_thread = Thread.current
138
+ job_thread[
139
+ "rufus_scheduler__trigger_thread__#{@scheduler.object_id}"
140
+ ] = true
141
+ @last_job_thread = job_thread
142
+
143
+ begin
144
+
145
+ trigger_block
146
+
147
+ job_thread = nil
148
+ to_job.unschedule if to_job
149
+
150
+ @running = false
151
+
152
+ rescue Exception => e
153
+
154
+ @scheduler.handle_exception(self, e)
155
+ end
156
+ end
157
+
158
+ # note that add_job and add_cron_job ensured that :blocking is
159
+ # not used along :timeout
160
+
161
+ if to = @params[:timeout]
162
+
163
+ to_job = @scheduler.in(to, :parent => self, :tags => 'timeout') do
164
+
165
+ # at this point, @job_thread might be set
166
+
167
+ if job_thread && job_thread.alive?
168
+ job_thread.raise(Rufus::Scheduler::TimeOutError)
169
+ end
170
+ end
171
+ end
172
+
173
+ end
174
+
175
+ # Simply encapsulating the block#call/trigger operation, for easy
176
+ # override.
177
+ #
178
+ def trigger_block
179
+
180
+ @block.respond_to?(:call) ?
181
+ @block.call(self) : @block.trigger(@params.merge(:job => self))
182
+ end
183
+
184
+ # Unschedules this job.
185
+ #
186
+ def unschedule
187
+
188
+ @scheduler.unschedule(self.job_id)
189
+ end
190
+ end
191
+
192
+ #
193
+ # The base class of at/in/every jobs.
194
+ #
195
+ class SimpleJob < Job
196
+
197
+ # When the job is supposed to trigger
198
+ #
199
+ attr_reader :at
200
+
201
+ attr_reader :last
202
+
203
+ def determine_at
204
+
205
+ @at = Rufus.at_to_f(@t)
206
+ end
207
+
208
+ # Returns the next time (or the unique time) this job is meant to trigger
209
+ #
210
+ def next_time
211
+
212
+ Time.at(@at)
213
+ end
214
+ end
215
+
216
+ #
217
+ # Job that occurs once, in a certain amount of time.
218
+ #
219
+ class InJob < SimpleJob
220
+
221
+ # If this InJob is a timeout job, parent points to the job that
222
+ # is subject to the timeout.
223
+ #
224
+ attr_reader :parent
225
+
226
+ def initialize (scheduler, t, params)
227
+ @parent = params[:parent]
228
+ super
229
+ end
230
+
231
+ protected
232
+
233
+ def determine_at
234
+
235
+ iin = @t.is_a?(Fixnum) || @t.is_a?(Float) ?
236
+ @t : Rufus.parse_duration_string(@t)
237
+
238
+ @at = (Time.now + iin).to_f
239
+ end
240
+ end
241
+
242
+ #
243
+ # Job that occurs once, at a certain point in time.
244
+ #
245
+ class AtJob < SimpleJob
246
+ end
247
+
248
+ #
249
+ # Recurring job with a certain frequency.
250
+ #
251
+ class EveryJob < SimpleJob
252
+
253
+ # The frequency, in seconds, of this EveryJob
254
+ #
255
+ attr_reader :frequency
256
+
257
+ def initialize (scheduler, t, params, &block)
258
+ super
259
+ determine_frequency
260
+ determine_at
261
+ end
262
+
263
+ # Triggers the job (and reschedules it).
264
+ #
265
+ def trigger
266
+
267
+ schedule_next
268
+
269
+ super
270
+ end
271
+
272
+ protected
273
+
274
+ def determine_frequency
275
+
276
+ @frequency = @t.is_a?(Fixnum) || @t.is_a?(Float) ?
277
+ @t : Rufus.parse_duration_string(@t)
278
+ end
279
+
280
+ def determine_at
281
+
282
+ return unless @frequency
283
+
284
+ @last = @at
285
+ # the first time, @last will be nil
286
+
287
+ @at = if @last
288
+ @last + @frequency
289
+ else
290
+ if fi = @params[:first_in]
291
+ Time.now.to_f + Rufus.duration_to_f(fi)
292
+ elsif fa = @params[:first_at]
293
+ Rufus.at_to_f(fa)
294
+ else
295
+ Time.now.to_f + @frequency
296
+ end
297
+ end
298
+ end
299
+
300
+ # It's an every job, have to schedule next time it occurs...
301
+ #
302
+ def schedule_next
303
+
304
+ determine_at
305
+
306
+ @scheduler.send(:add_job, self)
307
+ end
308
+ end
309
+
310
+ #
311
+ # Recurring job, cron style.
312
+ #
313
+ class CronJob < Job
314
+
315
+ # The CronLine instance, it holds all the info about the cron schedule
316
+ #
317
+ attr_reader :cron_line
318
+
319
+ # The job parameters (passed via the schedule method)
320
+ #
321
+ attr_reader :params
322
+
323
+ # The block to call when triggering
324
+ #
325
+ attr_reader :block
326
+
327
+ # Creates a new CronJob instance.
328
+ #
329
+ def initialize (scheduler, cron_string, params, &block)
330
+
331
+ super
332
+
333
+ @cron_line = case @t
334
+
335
+ when String then CronLine.new(@t)
336
+ when CronLine then @t
337
+
338
+ else raise "cannot initialize a CronJob out of #{@t.inspect}"
339
+ end
340
+ end
341
+
342
+ def trigger_if_matches (time)
343
+
344
+ trigger(time) if @cron_line.matches?(time)
345
+ end
346
+
347
+ # Returns the next time this job is meant to trigger
348
+ #
349
+ def next_time (from=Time.now)
350
+
351
+ @cron_line.next_time(from)
352
+ end
353
+
354
+ protected
355
+
356
+ def determine_at
357
+ # empty
358
+ end
359
+ end
360
+
361
+ end
362
+ end
363
+
@@ -0,0 +1,365 @@
1
+ #--
2
+ # Copyright (c) 2005-2010, 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
+ # Hecho en Costa Rica
23
+ #++
24
+
25
+
26
+ require 'date'
27
+ #require 'parsedate'
28
+
29
+
30
+ module Rufus
31
+
32
+ # Returns the current time as an ISO date string
33
+ #
34
+ def Rufus.now
35
+
36
+ to_iso8601_date(Time.new())
37
+ end
38
+
39
+ # As the name implies.
40
+ #
41
+ def Rufus.to_iso8601_date (date)
42
+
43
+ date = case date
44
+ when Date then date
45
+ when Float then to_datetime(Time.at(date))
46
+ when Time then to_datetime(date)
47
+ else DateTime.parse(date)
48
+ end
49
+
50
+ s = date.to_s # this is costly
51
+ s[10] = ' '
52
+
53
+ s
54
+ end
55
+
56
+ # the old method we used to generate our ISO datetime strings
57
+ #
58
+ def Rufus.time_to_iso8601_date (time)
59
+
60
+ s = time.getutc().strftime(TIME_FORMAT)
61
+ o = time.utc_offset / 3600
62
+ o = "#{o}00"
63
+ o = "0#{o}" if o.length < 4
64
+ o = "+#{o}" unless o[0..1] == '-'
65
+
66
+ "#{s} #{o}"
67
+ end
68
+
69
+ # Returns a Ruby time
70
+ #
71
+ def Rufus.to_ruby_time (sdate)
72
+
73
+ DateTime.parse(sdate)
74
+ end
75
+
76
+ # Equivalent to java.lang.System.currentTimeMillis()
77
+ #
78
+ def Rufus.current_time_millis
79
+
80
+ (Time.new.to_f * 1000).to_i
81
+ end
82
+
83
+ FLOAT_DURATION = /^\d*\.\d*$/
84
+
85
+ # Turns a string like '1m10s' into a float like '70.0', more formally,
86
+ # turns a time duration expressed as a string into a Float instance
87
+ # (millisecond count).
88
+ #
89
+ # w -> week
90
+ # d -> day
91
+ # h -> hour
92
+ # m -> minute
93
+ # s -> second
94
+ # M -> month
95
+ # y -> year
96
+ # 'nada' -> millisecond
97
+ #
98
+ # Some examples :
99
+ #
100
+ # Rufus.parse_time_string "0.5" # => 0.5
101
+ # Rufus.parse_time_string "500" # => 0.5
102
+ # Rufus.parse_time_string "1000" # => 1.0
103
+ # Rufus.parse_time_string "1h" # => 3600.0
104
+ # Rufus.parse_time_string "1h10s" # => 3610.0
105
+ # Rufus.parse_time_string "1w2d" # => 777600.0
106
+ #
107
+ def Rufus.parse_time_string (string)
108
+
109
+ return string.to_f if FLOAT_DURATION.match(string)
110
+
111
+ string = string.strip
112
+
113
+ index = -1
114
+ result = 0.0
115
+
116
+ number = ''
117
+
118
+ loop do
119
+
120
+ index = index + 1
121
+
122
+ if index >= string.length
123
+ result = result + (Float(number) / 1000.0) if number.length > 0
124
+ break
125
+ end
126
+
127
+ c = string[index, 1]
128
+
129
+ if (c >= '0' and c <= '9')
130
+ number = number + c
131
+ next
132
+ end
133
+
134
+ value = Integer(number)
135
+ number = ''
136
+
137
+ multiplier = DURATIONS[c]
138
+
139
+ raise "unknown time char '#{c}'" unless multiplier
140
+
141
+ result = result + (value * multiplier)
142
+ end
143
+
144
+ result
145
+ end
146
+
147
+ class << self
148
+ alias_method :parse_duration_string, :parse_time_string
149
+ end
150
+
151
+ #
152
+ # conversion methods between Date[Time] and Time
153
+
154
+ #--
155
+ # Ruby Cookbook 1st edition p.111
156
+ # http://www.oreilly.com/catalog/rubyckbk/
157
+ # a must
158
+ #++
159
+
160
+ # Converts a Time instance to a DateTime one
161
+ #
162
+ def Rufus.to_datetime (time)
163
+
164
+ s = time.sec + Rational(time.usec, 10**6)
165
+ o = Rational(time.utc_offset, 3600 * 24)
166
+
167
+ begin
168
+
169
+ DateTime.new(
170
+ time.year,
171
+ time.month,
172
+ time.day,
173
+ time.hour,
174
+ time.min,
175
+ s,
176
+ o)
177
+
178
+ rescue Exception => e
179
+
180
+ DateTime.new(
181
+ time.year,
182
+ time.month,
183
+ time.day,
184
+ time.hour,
185
+ time.min,
186
+ time.sec,
187
+ time.utc_offset)
188
+ end
189
+ end
190
+
191
+ def Rufus.to_gm_time (dtime)
192
+
193
+ to_ttime(dtime.new_offset, :gm)
194
+ end
195
+
196
+ def Rufus.to_local_time (dtime)
197
+
198
+ to_ttime(dtime.new_offset(DateTime.now.offset-offset), :local)
199
+ end
200
+
201
+ def Rufus.to_ttime (d, method)
202
+
203
+ usec = (d.sec_fraction * 3600 * 24 * (10**6)).to_i
204
+ Time.send(method, d.year, d.month, d.day, d.hour, d.min, d.sec, usec)
205
+ end
206
+
207
+ # Turns a number of seconds into a a time string
208
+ #
209
+ # Rufus.to_duration_string 0 # => '0s'
210
+ # Rufus.to_duration_string 60 # => '1m'
211
+ # Rufus.to_duration_string 3661 # => '1h1m1s'
212
+ # Rufus.to_duration_string 7 * 24 * 3600 # => '1w'
213
+ # Rufus.to_duration_string 30 * 24 * 3600 + 1 # => "4w2d1s"
214
+ #
215
+ # It goes from seconds to the year. Months are not counted (as they
216
+ # are of variable length). Weeks are counted.
217
+ #
218
+ # For 30 days months to be counted, the second parameter of this
219
+ # method can be set to true.
220
+ #
221
+ # Rufus.to_time_string 30 * 24 * 3600 + 1, true # => "1M1s"
222
+ #
223
+ # (to_time_string is an alias for to_duration_string)
224
+ #
225
+ # If a Float value is passed, milliseconds will be displayed without
226
+ # 'marker'
227
+ #
228
+ # Rufus.to_duration_string 0.051 # =>"51"
229
+ # Rufus.to_duration_string 7.051 # =>"7s51"
230
+ # Rufus.to_duration_string 0.120 + 30 * 24 * 3600 + 1 # =>"4w2d1s120"
231
+ #
232
+ # (this behaviour mirrors the one found for parse_time_string()).
233
+ #
234
+ # Options are :
235
+ #
236
+ # * :months, if set to true, months (M) of 30 days will be taken into
237
+ # account when building up the result
238
+ # * :drop_seconds, if set to true, seconds and milliseconds will be trimmed
239
+ # from the result
240
+ #
241
+ def Rufus.to_duration_string (seconds, options={})
242
+
243
+ return (options[:drop_seconds] ? '0m' : '0s') if seconds <= 0
244
+
245
+ h = to_duration_hash seconds, options
246
+
247
+ s = DU_KEYS.inject('') do |r, key|
248
+ count = h[key]
249
+ count = nil if count == 0
250
+ r << "#{count}#{key}" if count
251
+ r
252
+ end
253
+
254
+ ms = h[:ms]
255
+ s << ms.to_s if ms
256
+
257
+ s
258
+ end
259
+
260
+ class << self
261
+ alias_method :to_time_string, :to_duration_string
262
+ end
263
+
264
+ # Turns a number of seconds (integer or Float) into a hash like in :
265
+ #
266
+ # Rufus.to_duration_hash 0.051
267
+ # # => { :ms => "51" }
268
+ # Rufus.to_duration_hash 7.051
269
+ # # => { :s => 7, :ms => "51" }
270
+ # Rufus.to_duration_hash 0.120 + 30 * 24 * 3600 + 1
271
+ # # => { :w => 4, :d => 2, :s => 1, :ms => "120" }
272
+ #
273
+ # This method is used by to_duration_string (to_time_string) behind
274
+ # the scene.
275
+ #
276
+ # Options are :
277
+ #
278
+ # * :months, if set to true, months (M) of 30 days will be taken into
279
+ # account when building up the result
280
+ # * :drop_seconds, if set to true, seconds and milliseconds will be trimmed
281
+ # from the result
282
+ #
283
+ def Rufus.to_duration_hash (seconds, options={})
284
+
285
+ h = {}
286
+
287
+ if seconds.is_a?(Float)
288
+ h[:ms] = (seconds % 1 * 1000).to_i
289
+ seconds = seconds.to_i
290
+ end
291
+
292
+ if options[:drop_seconds]
293
+ h.delete :ms
294
+ seconds = (seconds - seconds % 60)
295
+ end
296
+
297
+ durations = options[:months] ? DURATIONS2M : DURATIONS2
298
+
299
+ durations.each do |key, duration|
300
+
301
+ count = seconds / duration
302
+ seconds = seconds % duration
303
+
304
+ h[key.to_sym] = count if count > 0
305
+ end
306
+
307
+ h
308
+ end
309
+
310
+ # Ensures that a duration is a expressed as a Float instance.
311
+ #
312
+ # duration_to_f("10s")
313
+ #
314
+ # will yield 10.0
315
+ #
316
+ def Rufus.duration_to_f (s)
317
+
318
+ return s if s.kind_of?(Float)
319
+ return parse_time_string(s) if s.kind_of?(String)
320
+ Float(s.to_s)
321
+ end
322
+
323
+ #
324
+ # Ensures an 'at' value is translated to a float
325
+ # (to be compared with the float coming from time.to_f)
326
+ #
327
+ def Rufus.at_to_f (at)
328
+
329
+ # TODO : use chronic if present
330
+
331
+ at = Rufus::to_ruby_time(at) if at.is_a?(String)
332
+ at = Rufus::to_gm_time(at) if at.is_a?(DateTime)
333
+ #at = at.to_f if at.is_a?(Time)
334
+ at = at.to_f if at.respond_to?(:to_f)
335
+
336
+ raise ArgumentError.new(
337
+ "cannot determine 'at' time from : #{at.inspect}"
338
+ ) unless at.is_a?(Float)
339
+
340
+ at
341
+ end
342
+
343
+ protected
344
+
345
+ DURATIONS2M = [
346
+ [ 'y', 365 * 24 * 3600 ],
347
+ [ 'M', 30 * 24 * 3600 ],
348
+ [ 'w', 7 * 24 * 3600 ],
349
+ [ 'd', 24 * 3600 ],
350
+ [ 'h', 3600 ],
351
+ [ 'm', 60 ],
352
+ [ 's', 1 ]
353
+ ]
354
+ DURATIONS2 = DURATIONS2M.dup
355
+ DURATIONS2.delete_at(1)
356
+
357
+ DURATIONS = DURATIONS2M.inject({}) do |r, (k, v)|
358
+ r[k] = v
359
+ r
360
+ end
361
+
362
+ DU_KEYS = DURATIONS2M.collect { |k, v| k.to_sym }
363
+
364
+ end
365
+