em-timers 0.2.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: fd2dd3c97deb96f7c3d9f2681bc9d7a8f03e5431
4
+ data.tar.gz: aa73bcc16297daddf064b76ec2cb84815d3d7f10
5
+ SHA512:
6
+ metadata.gz: 41c3dc4a4a28c1e8dcc790b5a7410e69d02bb18a08da38dbdb4e2f172987b8f3d5b6265e52070aa605245fb7eb8ce2fce414621d825a4d5071a4cde273550456
7
+ data.tar.gz: 42a47c94053fef639e74d7d952dce3a217a98ceff71f65e1231f5a56d5384481c29983435a8303e9f3b6c26525195388aee25c11bf8d2f4aff4e5c5fb637a6eb
data/README ADDED
@@ -0,0 +1,39 @@
1
+ em-timers
2
+
3
+ helper methods for timers in EventMachine
4
+
5
+ examples:
6
+
7
+ # Once per hour, starting now. Note the :now symbol must be passed for your block to be called immediately.
8
+ EM::Timers.do_hourly(:starting => :now) { puts "drink a beer" }
9
+
10
+ # Once per day, starting in 1 day.
11
+ EM::Timers.do_daily { puts "take a shower" }
12
+
13
+ # Once per week, starting in 10 hours
14
+ EM::Timers.do_weekly(:starting => 10.hours.from_now) { puts "take out the garbage" }
15
+
16
+ # Once per month, starting at this time tomorrow.
17
+ EM::Timers.do_monthly(:starting => 1.day.from_now) { puts "pay the bills" }
18
+
19
+ # Or you can simply pass a number of seconds til starting
20
+ EM::Timers.do_monthly(:starting => 2592000) { puts "pay the bills, in seconds!" }
21
+
22
+ # Leveraging the Chronic time parsing library. em-timers knows if you have required
23
+ # Chronic and uses it to parse strings if you have. Otherwise, it uses Time.parse.
24
+ #
25
+ # Every 2 hours, starting next Tuesday at 9AM.
26
+ require 'chronic'
27
+ EM::Timers.do(:every => 2.hours, :starting => 'next tuesday at 9am') { puts "go pee" }
28
+
29
+ # You can add up units of time as well
30
+ EM::Timers.do(:every => 1.minutes + 3.seconds) { puts "hi!" }
31
+
32
+ # You can list and cancel timers, too
33
+ EM::Timers.list.each { |timer|
34
+ timer.cancel
35
+ }
36
+
37
+
38
+ jakecdouglas@gmail.com
39
+ yakischloba on Freenode #eventmachine
data/lib/em-timers.rb ADDED
@@ -0,0 +1,8 @@
1
+ $:.unshift File.expand_path(File.dirname(File.expand_path(__FILE__)))
2
+ require 'eventmachine'
3
+ require 'time'
4
+ require 'em-timers/em-timers'
5
+ require 'em-timers/numericmixable'
6
+ require 'em-timers/cron_line'
7
+
8
+ Numeric.send :include, NumericMixable
@@ -0,0 +1,251 @@
1
+ #--
2
+ # Copyright (c) 2006-2009, 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 EventMachine
27
+
28
+ module Timers
29
+
30
+ #
31
+ # A 'cron line' is a line in the sense of a crontab
32
+ # (man 5 crontab) file line.
33
+ #
34
+ class CronLine
35
+
36
+ #
37
+ # The string used for creating this cronline instance.
38
+ #
39
+ attr_reader :original
40
+
41
+ attr_reader \
42
+ :seconds,
43
+ :minutes,
44
+ :hours,
45
+ :days,
46
+ :months,
47
+ :weekdays
48
+
49
+ def initialize (line)
50
+
51
+ super()
52
+
53
+ @original = line
54
+
55
+ items = line.split
56
+
57
+ unless items.length == 5 or items.length == 6
58
+ raise(
59
+ "cron '#{line}' string should hold 5 or 6 items, not #{items.length}")
60
+ end
61
+
62
+ offset = items.length - 5
63
+
64
+ @seconds = offset == 1 ? parse_item(items[0], 0, 59) : [ 0 ]
65
+ @minutes = parse_item(items[0 + offset], 0, 59)
66
+ @hours = parse_item(items[1 + offset], 0, 24)
67
+ @days = parse_item(items[2 + offset], 1, 31)
68
+ @months = parse_item(items[3 + offset], 1, 12)
69
+ @weekdays = parse_weekdays(items[4 + offset])
70
+ end
71
+
72
+ #
73
+ # Returns true if the given time matches this cron line.
74
+ #
75
+ def matches? (time)
76
+
77
+ time = Time.at(time) unless time.kind_of?(Time)
78
+
79
+ return false unless sub_match?(time.sec, @seconds)
80
+ return false unless sub_match?(time.min, @minutes)
81
+ return false unless sub_match?(time.hour, @hours)
82
+ return false unless sub_match?(time.day, @days)
83
+ return false unless sub_match?(time.month, @months)
84
+ return false unless sub_match?(time.wday, @weekdays)
85
+ true
86
+ end
87
+
88
+ #
89
+ # Returns an array of 6 arrays (seconds, minutes, hours, days,
90
+ # months, weekdays).
91
+ # This method is used by the cronline unit tests.
92
+ #
93
+ def to_array
94
+
95
+ [ @seconds, @minutes, @hours, @days, @months, @weekdays ]
96
+ end
97
+
98
+ #
99
+ # Returns the next time that this cron line is supposed to 'fire'
100
+ #
101
+ # This is raw, 3 secs to iterate over 1 year on my macbook :( brutal.
102
+ #
103
+ # This method accepts an optional Time parameter. It's the starting point
104
+ # for the 'search'. By default, it's Time.now
105
+ #
106
+ # Note that the time instance returned will be in the same time zone that
107
+ # the given start point Time (thus a result in the local time zone will
108
+ # be passed if no start time is specified (search start time set to
109
+ # Time.now))
110
+ #
111
+ # >> EventMachine::Timers::CronLine.new('30 7 * * *').next_time( Time.mktime(2008,10,24,7,29) )
112
+ # => Fri Oct 24 07:30:00 -0500 2008
113
+ #
114
+ # >> EventMachine::Timers::CronLine.new('30 7 * * *').next_time( Time.utc(2008,10,24,7,29) )
115
+ # => Fri Oct 24 07:30:00 UTC 2008
116
+ #
117
+ # >> EventMachine::Timers::CronLine.new('30 7 * * *').next_time( Time.utc(2008,10,24,7,29) ).localtime
118
+ # => Fri Oct 24 02:30:00 -0500 2008
119
+ #
120
+ # (Thanks to K Liu for the note and the examples)
121
+ #
122
+ def next_time (time=Time.now)
123
+
124
+ time -= time.usec * 1e-6
125
+ time += 1
126
+
127
+ loop do
128
+
129
+ unless date_match?(time)
130
+ time += (24 - time.hour) * 3600 - time.min * 60 - time.sec
131
+ next
132
+ end
133
+
134
+ unless sub_match?(time.hour, @hours)
135
+ time += (60 - time.min) * 60 - time.sec
136
+ next
137
+ end
138
+
139
+ unless sub_match?(time.min, @minutes)
140
+ time += 60 - time.sec
141
+ next
142
+ end
143
+
144
+ unless sub_match?(time.sec, @seconds)
145
+ time += 1
146
+ next
147
+ end
148
+
149
+ break
150
+ end
151
+
152
+ time
153
+ end
154
+
155
+ private
156
+
157
+ WDS = %w[ sun mon tue wed thu fri sat ]
158
+ #
159
+ # used by parse_weekday()
160
+
161
+ def parse_weekdays (item)
162
+
163
+ item = item.downcase
164
+
165
+ WDS.each_with_index { |day, index| item = item.gsub(day, index.to_s) }
166
+
167
+ r = parse_item(item, 0, 7)
168
+
169
+ r.is_a?(Array) ?
170
+ r.collect { |e| e == 7 ? 0 : e }.uniq :
171
+ r
172
+ end
173
+
174
+ def parse_item (item, min, max)
175
+
176
+ return nil if item == '*'
177
+ return parse_list(item, min, max) if item.index(',')
178
+ return parse_range(item, min, max) if item.index('*') or item.index('-')
179
+
180
+ i = Integer(item)
181
+
182
+ i = min if i < min
183
+ i = max if i > max
184
+
185
+ [ i ]
186
+ end
187
+
188
+ def parse_list (item, min, max)
189
+
190
+ item.split(',').inject([]) { |r, i|
191
+ r.push(parse_range(i, min, max))
192
+ }.flatten
193
+ end
194
+
195
+ def parse_range (item, min, max)
196
+
197
+ i = item.index('-')
198
+ j = item.index('/')
199
+
200
+ return item.to_i if (not i and not j)
201
+
202
+ inc = j ? Integer(item[j+1..-1]) : 1
203
+
204
+ istart = -1
205
+ iend = -1
206
+
207
+ if i
208
+
209
+ istart = Integer(item[0..i - 1])
210
+
211
+ if j
212
+ iend = Integer(item[i + 1..j])
213
+ else
214
+ iend = Integer(item[i + 1..-1])
215
+ end
216
+
217
+ else # case */x
218
+
219
+ istart = min
220
+ iend = max
221
+ end
222
+
223
+ istart = min if istart < min
224
+ iend = max if iend > max
225
+
226
+ result = []
227
+
228
+ value = istart
229
+ loop do
230
+ result << value
231
+ value = value + inc
232
+ break if value > iend
233
+ end
234
+
235
+ result
236
+ end
237
+
238
+ def sub_match?(value, values)
239
+ values.nil? || values.include?(value)
240
+ end
241
+
242
+ def date_match?(date)
243
+ return false unless sub_match?(date.day, @days)
244
+ return false unless sub_match?(date.month, @months)
245
+ return false unless sub_match?(date.wday, @weekdays)
246
+ true
247
+ end
248
+ end
249
+
250
+ end
251
+ end
@@ -0,0 +1,164 @@
1
+ module EventMachine
2
+
3
+ module Timers
4
+
5
+ class Timer
6
+ @list = []
7
+ class << self
8
+ attr :list, false
9
+ end
10
+ attr :tag, false
11
+ attr :name, false
12
+ attr :options, true
13
+ attr :reschedule_timer, false
14
+ attr :kickoff_timer, false
15
+ def initialize(options, block)
16
+ @options = options
17
+ if options[:tag]
18
+ @tag = options[:tag]
19
+ end
20
+ if options[:name]
21
+ @name = options[:name]
22
+ end
23
+ @block = block
24
+ @repeats = true
25
+ Timer.list << self
26
+ end
27
+ def cancel
28
+ if @kickoff_timer
29
+ EM::cancel_timer(@kickoff_timer)
30
+ @kickoff_timer = nil
31
+ end
32
+ if @reschedule_timer
33
+ EM::cancel_timer(@reschedule_timer)
34
+ @reschedule_timer = nil
35
+ end
36
+ Timer.list.delete(self)
37
+ end
38
+ def schedule
39
+ increment = @options[:every] || 1
40
+ starting = @options[:starting]
41
+
42
+ if options[:cron]
43
+ starting =
44
+ EventMachine::Timers::CronLine.new(options[:cron]).next_time
45
+ @kickoff_timer = EM.add_timer(calculate_delta_time(starting)) {
46
+ @kickoff_timer = nil
47
+ run_and_reschedule_cron
48
+ }
49
+ return self
50
+ elsif @options[:at] || @options[:in]
51
+ @repeats = false
52
+ if @options[:at]
53
+ starting = parse_time(@options[:at])
54
+ else
55
+ starting = Time.now + parse_time(@options[:in])
56
+ end
57
+ end
58
+
59
+ if starting == :now
60
+ run_and_reschedule(increment)
61
+ return self
62
+ else
63
+ starting = parse_time(starting)
64
+ end
65
+
66
+ time = calculate_delta_time(starting)
67
+
68
+ @kickoff_timer = EM.add_timer(time) {
69
+ @kickoff_timer = nil
70
+ run_and_reschedule(increment)
71
+ }
72
+ self
73
+ end
74
+
75
+ protected
76
+
77
+ def calculate_delta_time(the_time)
78
+ increment = @options[:every] || 1
79
+ if the_time.is_a?(Time)
80
+ time = the_time - Time.now
81
+ else
82
+ time = the_time
83
+ end
84
+ while time < 0
85
+ time += increment
86
+ end
87
+ time
88
+ end
89
+
90
+ def parse_time(some_time)
91
+ if some_time.is_a?(String)
92
+ Object.const_defined?("Chronic") ?
93
+ Chronic.parse(some_time) :
94
+ Time.parse(some_time)
95
+ elsif some_time.is_a?(Time) || some_time.kind_of?(Numeric)
96
+ some_time
97
+ else
98
+ Time.now
99
+ end
100
+ end
101
+
102
+ def run_and_reschedule_cron
103
+ starting =
104
+ EventMachine::Timers::CronLine.new(options[:cron]).next_time
105
+ @reschedule_timer = EM.add_timer(calculate_delta_time(starting)) {
106
+ run_and_reschedule_cron
107
+ }
108
+ @block.call
109
+ end
110
+
111
+ def run_and_reschedule(inc)
112
+ if @repeats
113
+ @reschedule_timer = EM.add_timer(inc) {
114
+ run_and_reschedule(inc)
115
+ }
116
+ end
117
+ @block.call
118
+ end
119
+
120
+ end
121
+
122
+ def self.do(options={}, &blk)
123
+ Timer.new(options, blk).schedule
124
+ end
125
+
126
+ def self.do_hourly(options={}, &blk)
127
+ self.do({:every => 1.hour}.merge(options), &blk)
128
+ end
129
+
130
+ def self.do_daily(options={}, &blk)
131
+ self.do({:every => 1.day}.merge(options), &blk)
132
+ end
133
+
134
+ def self.do_weekly(options={}, &blk)
135
+ self.do({:every => 1.week}.merge(options), &blk)
136
+ end
137
+
138
+ def self.do_monthly(options={}, &blk)
139
+ self.do({:every => 1.month}.merge(options), &blk)
140
+ end
141
+
142
+ def self.cron(spec, &blk)
143
+ self.do({:cron => spec}, &blk)
144
+ end
145
+
146
+ def self.list
147
+ Timer.list
148
+ end
149
+
150
+ def self.find_by_tag(tag)
151
+ Timer.list.find_all { |t|
152
+ t.tag == tag
153
+ }
154
+ end
155
+
156
+ def self.find_by_name(name)
157
+ Timer.list.find_all { |t|
158
+ t.name == name
159
+ }
160
+ end
161
+
162
+ end
163
+
164
+ end
@@ -0,0 +1,56 @@
1
+ # Stolen directly from ramaze/snippets
2
+ module NumericMixable
3
+ def seconds
4
+ self
5
+ end
6
+ alias second seconds
7
+
8
+ # 60 seconds in a minute
9
+ def minutes
10
+ self * 60
11
+ end
12
+ alias minute minutes
13
+
14
+ # 60 minutes in an hour
15
+ def hours
16
+ self * 3600
17
+ end
18
+ alias hour hours
19
+
20
+ # 24 hours in a day
21
+ def days
22
+ self * 86400
23
+ end
24
+ alias day days
25
+
26
+ # 7 days in a week
27
+ def weeks
28
+ self * 604800
29
+ end
30
+ alias week weeks
31
+
32
+ # 30 days in a month
33
+ def months
34
+ self * 2592000
35
+ end
36
+ alias month months
37
+
38
+ # 365.25 days in a year
39
+ def years
40
+ self * 883612800
41
+ end
42
+ alias year years
43
+
44
+ # Time in the past, i.e. 3.days.ago
45
+ def ago t = Time.now
46
+ t - self
47
+ end
48
+ alias before ago
49
+
50
+ # Time in the future, i.e. 3.days.from_now
51
+ def from_now t = Time.now
52
+ t + self
53
+ end
54
+ alias since from_now
55
+
56
+ end
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: em-timers
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Jake Douglas
8
+ - Colin Steele
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2009-11-23 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: eventmachine
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - '>='
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - '>='
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ description: helper methods for timers in EventMachine
29
+ email: jakecdouglas@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - README
35
+ - lib/em-timers.rb
36
+ - lib/em-timers/numericmixable.rb
37
+ - lib/em-timers/cron_line.rb
38
+ - lib/em-timers/em-timers.rb
39
+ homepage: http://www.github.com/yakischloba/em-timers
40
+ licenses: []
41
+ metadata: {}
42
+ post_install_message:
43
+ rdoc_options: []
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - '>='
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ requirements: []
57
+ rubyforge_project:
58
+ rubygems_version: 2.0.5
59
+ signing_key:
60
+ specification_version: 4
61
+ summary: helper methods for timers in EventMachine
62
+ test_files: []