tanzeeb-rufus-scheduler 2.0.7.2

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