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,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
+