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,86 @@
1
+
2
+ require 'rubygems'
3
+ require 'rake'
4
+
5
+
6
+ load 'lib/rufus/sc/version.rb'
7
+
8
+
9
+ #
10
+ # CLEAN
11
+
12
+ require 'rake/clean'
13
+ CLEAN.include('pkg', 'tmp', 'html')
14
+ task :default => [ :clean ]
15
+
16
+
17
+ #
18
+ # GEM
19
+
20
+ require 'jeweler'
21
+
22
+ Jeweler::Tasks.new do |gem|
23
+
24
+ gem.version = Rufus::Scheduler::VERSION
25
+ gem.name = 'tanzeeb-rufus-scheduler'
26
+ gem.summary = 'job scheduler for Ruby (at, cron, in and every jobs)'
27
+
28
+ gem.description = %{
29
+ job scheduler for Ruby (at, cron, in and every jobs).
30
+
31
+ By default uses a Ruby thread, if EventMachine is present, it will rely on it.
32
+
33
+ This fork adds timezone support to cron schedules.
34
+ }
35
+ gem.email = 'tanzeeb@gmail.com'
36
+ gem.homepage = 'http://github.com/tanzeeb/rufus-scheduler/'
37
+ gem.authors = [ 'John Mettraux', 'Tanzeeb Khalili', 'Matt Briggs', 'Sean Kirby' ]
38
+ gem.rubyforge_project = 'rufus'
39
+
40
+ gem.test_file = 'spec/spec.rb'
41
+
42
+ #gem.add_dependency 'yajl-ruby'
43
+ gem.add_development_dependency 'rake'
44
+ gem.add_development_dependency 'yard'
45
+ gem.add_development_dependency 'bacon'
46
+ gem.add_development_dependency 'jeweler'
47
+ gem.add_dependency 'tzinfo'
48
+
49
+ # gemspec spec : http://www.rubygems.org/read/chapter/20
50
+ end
51
+ Jeweler::GemcutterTasks.new
52
+
53
+
54
+ #
55
+ # DOC
56
+
57
+ begin
58
+
59
+ require 'yard'
60
+
61
+ YARD::Rake::YardocTask.new do |doc|
62
+ doc.options = [
63
+ '-o', 'html/rufus-scheduler', '--title',
64
+ "rufus-scheduler #{Rufus::Scheduler::VERSION}"
65
+ ]
66
+ end
67
+
68
+ rescue LoadError
69
+
70
+ task :yard do
71
+ abort "YARD is not available : sudo gem install yard"
72
+ end
73
+ end
74
+
75
+
76
+ #
77
+ # TO THE WEB
78
+
79
+ task :upload_website => [ :clean, :yard ] do
80
+
81
+ account = 'jmettraux@rubyforge.org'
82
+ webdir = '/var/www/gforge-projects/rufus'
83
+
84
+ sh "rsync -azv -e ssh html/rufus-scheduler #{account}:#{webdir}/"
85
+ end
86
+
@@ -0,0 +1,57 @@
1
+
2
+ [o] spec for jobs in the past (in and at)
3
+ [o] :discard_past
4
+
5
+ [o] every
6
+ [o] cron
7
+
8
+ [o] CHECK every and unschedule !!!
9
+
10
+ [o] :tags
11
+ [o] timeout feature (at/in/every/cron) in Job class
12
+
13
+ [o] :first_in, :first_at
14
+
15
+ [x] :dont_reschedule (or block returns false ?)
16
+
17
+ [o] [get_]jobs methods
18
+ [o] find methods
19
+
20
+ [x] CTRL-C during tests : allow, trap_int...
21
+
22
+ [o] 1.9
23
+ [o] j1.2.0
24
+
25
+ [o] revise trigger block arity
26
+ use a compatibility switch ? yes
27
+
28
+ [o] synchronize @cron_jobs ?
29
+
30
+ [o] why not : make it work even if EM is not present
31
+ EmScheduler < Scheduler
32
+ FiberScheduler < Scheduler
33
+
34
+ [x] :blocking => 'blockname' idea, mutex = @mutexes['blockname'] ...
35
+ [o] eventually, make sleep frequency customizable
36
+
37
+ [o] PlainScheduler : name thread
38
+
39
+ [o] document :blocking
40
+
41
+ [o] README.rdoc
42
+ [o] fix jruby120 --em
43
+
44
+ [o] handle_exception (job, e)
45
+
46
+ [o] Schedulable
47
+
48
+ [o] Rufus::Scheduler.start_new() : autodetect EM ?
49
+ [o] check :blocking and every (reschedule blocking...)
50
+ [o] document :thread_name scheduler option
51
+
52
+ [o] unify cron_jobs#trigger_matching_jobs(now) and jobs#job_to_trigger
53
+ [o] pluggable job queues
54
+
55
+ [ ] Joel's complaint about timeout jobs gone ballistic
56
+ [x] move trigger_job out of the scheduler
57
+
@@ -0,0 +1,3 @@
1
+
2
+ require 'rufus/sc/scheduler'
3
+
@@ -0,0 +1,3 @@
1
+
2
+ require 'rufus/sc/rtime.rb'
3
+
@@ -0,0 +1,281 @@
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
+ require 'tzinfo'
26
+
27
+ module Rufus
28
+
29
+ #
30
+ # A 'cron line' is a line in the sense of a crontab
31
+ # (man 5 crontab) file line.
32
+ #
33
+ class CronLine
34
+
35
+ #
36
+ # The string used for creating this cronline instance.
37
+ #
38
+ attr_reader :original
39
+
40
+ attr_reader \
41
+ :seconds,
42
+ :minutes,
43
+ :hours,
44
+ :days,
45
+ :months,
46
+ :weekdays,
47
+ :zone
48
+
49
+ def initialize (line)
50
+
51
+ super()
52
+
53
+ @original = line
54
+
55
+ times, zone = line.split(' : ')
56
+ items = times.split
57
+
58
+ @zone = parse_zone(zone.to_s)
59
+
60
+ if zone && !@zone
61
+ raise "zone '#{zone}' is an invalid timezone"
62
+ end
63
+
64
+ unless items.length == 5 or items.length == 6
65
+ raise(
66
+ "cron '#{line}' string should hold 5 or 6 items, not #{items.length}")
67
+ end
68
+
69
+ offset = items.length - 5
70
+
71
+ @seconds = offset == 1 ? parse_item(items[0], 0, 59) : [ 0 ]
72
+ @minutes = parse_item(items[0 + offset], 0, 59)
73
+ @hours = parse_item(items[1 + offset], 0, 24)
74
+ @days = parse_item(items[2 + offset], 1, 31)
75
+ @months = parse_item(items[3 + offset], 1, 12)
76
+ @weekdays = parse_weekdays(items[4 + offset])
77
+
78
+ end
79
+
80
+ #
81
+ # Returns true if the given time matches this cron line.
82
+ #
83
+ def matches? (time)
84
+ time = Time.at(time) unless time.kind_of?(Time)
85
+
86
+ if @zone
87
+ utc = time.utc?
88
+ time = time.getutc unless utc
89
+ time = @zone.utc_to_local time
90
+ end
91
+
92
+ [
93
+ sub_match?(time.sec, @seconds),
94
+ sub_match?(time.min, @minutes),
95
+ sub_match?(time.hour, @hours),
96
+ sub_match?(time.day, @days),
97
+ sub_match?(time.month, @months),
98
+ sub_match?(time.wday, @weekdays)
99
+ ].all?
100
+ end
101
+
102
+ #
103
+ # Returns an array of 6 arrays (seconds, minutes, hours, days,
104
+ # months, weekdays).
105
+ # This method is used by the cronline unit tests.
106
+ #
107
+ def to_array
108
+
109
+ [ @seconds, @minutes, @hours, @days, @months, @weekdays, (@zone.name if @zone) ]
110
+ end
111
+
112
+ #
113
+ # Returns the next time that this cron line is supposed to 'fire'
114
+ #
115
+ # This is raw, 3 secs to iterate over 1 year on my macbook :( brutal.
116
+ # (Well, I was wrong, takes 0.001 sec on 1.8.7 and 1.9.1)
117
+ #
118
+ # This method accepts an optional Time parameter. It's the starting point
119
+ # for the 'search'. By default, it's Time.now
120
+ #
121
+ # Note that the time instance returned will be in the same time zone that
122
+ # the given start point Time (thus a result in the local time zone will
123
+ # be passed if no start time is specified (search start time set to
124
+ # Time.now))
125
+ #
126
+ # >> Rufus::CronLine.new('30 7 * * *').next_time( Time.mktime(2008,10,24,7,29) )
127
+ # => Fri Oct 24 07:30:00 -0500 2008
128
+ #
129
+ # >> Rufus::CronLine.new('30 7 * * *').next_time( Time.utc(2008,10,24,7,29) )
130
+ # => Fri Oct 24 07:30:00 UTC 2008
131
+ #
132
+ # >> Rufus::CronLine.new('30 7 * * *').next_time( Time.utc(2008,10,24,7,29) ).localtime
133
+ # => Fri Oct 24 02:30:00 -0500 2008
134
+ #
135
+ # (Thanks to K Liu for the note and the examples)
136
+ #
137
+ def next_time (time=Time.now)
138
+
139
+ if @zone
140
+ utc = time.utc?
141
+ time = time.getutc unless utc
142
+ time = @zone.utc_to_local time
143
+ end
144
+
145
+ time -= time.usec * 1e-6
146
+ time += 1
147
+
148
+ loop do
149
+
150
+ unless date_match?(time)
151
+ time += (24 - time.hour) * 3600 - time.min * 60 - time.sec
152
+ next
153
+ end
154
+
155
+ unless sub_match?(time.hour, @hours)
156
+ time += (60 - time.min) * 60 - time.sec
157
+ next
158
+ end
159
+
160
+ unless sub_match?(time.min, @minutes)
161
+ time += 60 - time.sec
162
+ next
163
+ end
164
+
165
+ unless sub_match?(time.sec, @seconds)
166
+ time += 1
167
+ next
168
+ end
169
+
170
+ break
171
+ end
172
+
173
+ if @zone
174
+ time = @zone.local_to_utc time
175
+ time = time.getlocal unless utc
176
+ end
177
+
178
+ time
179
+ end
180
+
181
+ private
182
+
183
+ WDS = %w[ sun mon tue wed thu fri sat ]
184
+ #
185
+ # used by parse_weekday()
186
+
187
+ def parse_weekdays (item)
188
+
189
+ item = item.downcase
190
+
191
+ WDS.each_with_index { |day, index| item = item.gsub(day, index.to_s) }
192
+
193
+ r = parse_item(item, 0, 7)
194
+
195
+ r.is_a?(Array) ?
196
+ r.collect { |e| e == 7 ? 0 : e }.uniq :
197
+ r
198
+ end
199
+
200
+ def parse_item (item, min, max)
201
+
202
+ return nil if item == '*'
203
+ return parse_list(item, min, max) if item.index(',')
204
+ return parse_range(item, min, max) if item.index('*') or item.index('-')
205
+
206
+ i = Integer(item)
207
+
208
+ i = min if i < min
209
+ i = max if i > max
210
+
211
+ [ i ]
212
+ end
213
+
214
+ def parse_list (item, min, max)
215
+
216
+ item.split(',').inject([]) { |r, i|
217
+ r.push(parse_range(i, min, max))
218
+ }.flatten
219
+ end
220
+
221
+ def parse_range (item, min, max)
222
+
223
+ i = item.index('-')
224
+ j = item.index('/')
225
+
226
+ return item.to_i if (not i and not j)
227
+
228
+ inc = j ? Integer(item[j+1..-1]) : 1
229
+
230
+ istart = -1
231
+ iend = -1
232
+
233
+ if i
234
+
235
+ istart = Integer(item[0..i - 1])
236
+
237
+ if j
238
+ iend = Integer(item[i + 1..j])
239
+ else
240
+ iend = Integer(item[i + 1..-1])
241
+ end
242
+
243
+ else # case */x
244
+
245
+ istart = min
246
+ iend = max
247
+ end
248
+
249
+ istart = min if istart < min
250
+ iend = max if iend > max
251
+
252
+ result = []
253
+
254
+ value = istart
255
+ loop do
256
+ result << value
257
+ value = value + inc
258
+ break if value > iend
259
+ end
260
+
261
+ result
262
+ end
263
+
264
+ def parse_zone zone
265
+ TZInfo::Timezone.get(zone) rescue nil
266
+ end
267
+
268
+ def sub_match?(value, values)
269
+ values.nil? || values.include?(value)
270
+ end
271
+
272
+ def date_match?(date)
273
+ return false unless sub_match?(date.day, @days)
274
+ return false unless sub_match?(date.month, @months)
275
+ return false unless sub_match?(date.wday, @weekdays)
276
+ true
277
+ end
278
+ end
279
+
280
+ end
281
+
@@ -0,0 +1,160 @@
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
+ require 'thread'
27
+
28
+
29
+ module Rufus
30
+ module Scheduler
31
+
32
+ #
33
+ # Tracking at/in/every jobs.
34
+ #
35
+ # In order of trigger time.
36
+ #
37
+ class JobQueue
38
+
39
+ # Mapping :at|:in|:every to their respective job classes.
40
+ #
41
+ JOB_TYPES = {
42
+ :at => Rufus::Scheduler::AtJob,
43
+ :in => Rufus::Scheduler::InJob,
44
+ :every => Rufus::Scheduler::EveryJob
45
+ }
46
+
47
+ def initialize
48
+
49
+ @mutex = Mutex.new
50
+ @jobs = []
51
+ end
52
+
53
+ # Triggers all the jobs that are scheduled for 'now'.
54
+ #
55
+ def trigger_matching_jobs
56
+
57
+ now = Time.now
58
+
59
+ while job = job_to_trigger(now)
60
+ job.trigger
61
+ end
62
+ end
63
+
64
+ # Adds this job to the map.
65
+ #
66
+ def << (job)
67
+
68
+ @mutex.synchronize do
69
+ delete(job.job_id)
70
+ @jobs << job
71
+ @jobs.sort! { |j0, j1| j0.at <=> j1.at }
72
+ end
73
+ end
74
+
75
+ # Removes a job (given its id). Returns nil if the job was not found.
76
+ #
77
+ def unschedule (job_id)
78
+
79
+ @mutex.synchronize { delete(job_id) }
80
+ end
81
+
82
+ # Returns a mapping job_id => job
83
+ #
84
+ def to_h
85
+
86
+ @jobs.inject({}) { |h, j| h[j.job_id] = j; h }
87
+ end
88
+
89
+ # Returns a list of jobs of the given type (:at|:in|:every)
90
+ #
91
+ def select (type)
92
+
93
+ type = JOB_TYPES[type]
94
+ @jobs.select { |j| j.is_a?(type) }
95
+ end
96
+
97
+ def size
98
+
99
+ @jobs.size
100
+ end
101
+
102
+ protected
103
+
104
+ def delete (job_id)
105
+
106
+ j = @jobs.find { |j| j.job_id == job_id }
107
+ @jobs.delete(j) if j
108
+ end
109
+
110
+ # Returns the next job to trigger. Returns nil if none eligible.
111
+ #
112
+ def job_to_trigger (now)
113
+
114
+ @mutex.synchronize do
115
+ if @jobs.size > 0 && now.to_f >= @jobs.first.at
116
+ @jobs.shift
117
+ else
118
+ nil
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ #
125
+ # Tracking cron jobs.
126
+ #
127
+ class CronJobQueue < JobQueue
128
+
129
+ def initialize
130
+
131
+ super
132
+ @last_cron_second = nil
133
+ end
134
+
135
+ def trigger_matching_jobs
136
+
137
+ now = Time.now
138
+
139
+ return if now.sec == @last_cron_second
140
+ @last_cron_second = now.sec
141
+ #
142
+ # ensuring the crons are checked within 1 second (not 1.2 second)
143
+
144
+ jobs = @mutex.synchronize { @jobs.dup }
145
+
146
+ jobs.each { |job| job.trigger_if_matches(now) }
147
+ end
148
+
149
+ def << (job)
150
+
151
+ @mutex.synchronize do
152
+ delete(job.job_id)
153
+ @jobs << job
154
+ end
155
+ end
156
+ end
157
+
158
+ end
159
+ end
160
+