Repository navigation
Expand file tree
/
Copy pathclockwork.rb
More file actions
235 lines (190 loc) · 4.82 KB
/
Copy pathclockwork.rb
File metadata and controls
235 lines (190 loc) · 4.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
require 'logger'
require 'tzinfo'
module Clockwork
@@events = []
class At
class FailedToParse < StandardError; end;
NOT_SPECIFIED = nil
WDAYS = %w[sunday monday tuesday wednesday thursday friday saturday].map do |w|
[w, w.capitalize, w[0...3], w[0...3].capitalize]
end
def self.parse(at)
return unless at
case at
when /^([[:alpha:]]+)\s(.*)$/
ret = parse($2)
wday = WDAYS.find_index {|x| x.include?($1) }
raise FailedToParse, at if wday.nil?
ret.wday = wday
ret
when /^(\d{1,2}):(\d\d)$/
new($2.to_i, $1.to_i)
when /^\*{1,2}:(\d\d)$/
new($1.to_i)
else
raise FailedToParse, at
end
rescue ArgumentError
raise FailedToParse, at
end
attr_writer :min, :hour, :wday
def initialize(min, hour=NOT_SPECIFIED, wday=NOT_SPECIFIED)
if min.nil? || min < 0 || min > 59 ||
(hour != NOT_SPECIFIED && (hour < 0 || hour > 23)) ||
(wday != NOT_SPECIFIED && (wday < 0 || wday > 6))
raise ArgumentError
end
@min = min
@hour = hour
@wday = wday
end
def ready?(t)
t.min == @min and
(@hour == NOT_SPECIFIED or t.hour == @hour) and
(@wday == NOT_SPECIFIED or t.wday == @wday)
end
end
class Event
attr_accessor :job, :last
def initialize(period, job, block, options={})
@period = period
@job = job
@at = At.parse(options[:at])
@last = nil
@block = block
if options[:if]
if options[:if].respond_to?(:call)
@if = options[:if]
else
raise ArgumentError.new(':if expects a callable object, but #{options[:if]} does not respond to call')
end
end
if options[:thread]
@thread = options[:thread]
end
tz = options[:tz] || Clockwork.config[:tz]
@timezone = TZInfo::Timezone.get(tz) if tz
end
def to_s
@job
end
def convert_timezone(t)
@timezone ? @timezone.utc_to_local(t.dup.utc) : t
end
def time?(t)
t = convert_timezone(t)
elapsed_ready = (@last.nil? or (t - @last).to_i >= @period)
elapsed_ready and (@at.nil? or @at.ready?(t)) and (@if.nil? or @if.call(t))
end
def thread?
@thread
end
def thread_available?
Thread.list.count < Clockwork.config[:max_threads]
end
def run(t)
t = convert_timezone(t)
@last = t
if thread?
if thread_available?
Thread.new { execute }
else
log_error "Threads exhausted; skipping #{self}"
end
else
execute
end
end
def execute
@block.call(@job)
rescue => e
log_error e
end
def log_error(e)
Clockwork.config[:logger].error(e)
end
def exception_message(e)
msg = [ "Exception #{e.class} -> #{e.message}" ]
base = File.expand_path(Dir.pwd) + '/'
e.backtrace.each do |t|
msg << " #{File.expand_path(t).gsub(/#{base}/, '')}"
end
msg.join("\n")
end
end
def configure
yield(config)
end
def config
@@configuration
end
extend self
def default_configuration
{ :sleep_timeout => 1, :logger => Logger.new(STDOUT), :max_threads => 10 }
end
@@configuration = default_configuration
def handler(&block)
@@handler = block
end
class NoHandlerDefined < RuntimeError; end
def get_handler
raise NoHandlerDefined unless (defined?(@@handler) and @@handler)
@@handler
end
def every(period, job, options={}, &block)
if options[:at].respond_to?(:each)
each_options = options.clone
options[:at].each do |at|
each_options[:at] = at
register(period, job, block, each_options)
end
else
register(period, job, block, options)
end
end
def run
log "Starting clock for #{@@events.size} events: [ " + @@events.map { |e| e.to_s }.join(' ') + " ]"
loop do
tick
sleep(config[:sleep_timeout])
end
end
def log(msg)
config[:logger].info(msg)
end
def tick(t=Time.now)
to_run = @@events.select do |event|
event.time?(t)
end
to_run.each do |event|
log "Triggering '#{event}'"
event.run(t)
end
to_run
end
def clear!
@@events = []
@@handler = nil
@@configuration = Clockwork.default_configuration
end
private
def register(period, job, block, options)
event = Event.new(period, job, block || get_handler, options)
@@events << event
event
end
end
unless 1.respond_to?(:seconds)
class Numeric
def seconds; self; end
alias :second :seconds
def minutes; self * 60; end
alias :minute :minutes
def hours; self * 3600; end
alias :hour :hours
def days; self * 86400; end
alias :day :days
def weeks; self * 604800; end
alias :week :weeks
end
end