rufus-scheduler 2.0.24 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/CHANGELOG.txt +76 -0
  2. data/CREDITS.txt +23 -0
  3. data/LICENSE.txt +1 -1
  4. data/README.md +1439 -0
  5. data/Rakefile +1 -5
  6. data/TODO.txt +149 -55
  7. data/lib/rufus/{sc → scheduler}/cronline.rb +167 -53
  8. data/lib/rufus/scheduler/job_array.rb +92 -0
  9. data/lib/rufus/scheduler/jobs.rb +633 -0
  10. data/lib/rufus/scheduler/locks.rb +95 -0
  11. data/lib/rufus/scheduler/util.rb +306 -0
  12. data/lib/rufus/scheduler/zones.rb +174 -0
  13. data/lib/rufus/scheduler/zotime.rb +154 -0
  14. data/lib/rufus/scheduler.rb +608 -27
  15. data/rufus-scheduler.gemspec +6 -4
  16. data/spec/basics_spec.rb +54 -0
  17. data/spec/cronline_spec.rb +479 -152
  18. data/spec/error_spec.rb +139 -0
  19. data/spec/job_array_spec.rb +39 -0
  20. data/spec/job_at_spec.rb +58 -0
  21. data/spec/job_cron_spec.rb +128 -0
  22. data/spec/job_every_spec.rb +104 -0
  23. data/spec/job_in_spec.rb +20 -0
  24. data/spec/job_interval_spec.rb +68 -0
  25. data/spec/job_repeat_spec.rb +357 -0
  26. data/spec/job_spec.rb +498 -109
  27. data/spec/lock_custom_spec.rb +47 -0
  28. data/spec/lock_flock_spec.rb +47 -0
  29. data/spec/lock_lockfile_spec.rb +61 -0
  30. data/spec/lock_spec.rb +59 -0
  31. data/spec/parse_spec.rb +263 -0
  32. data/spec/schedule_at_spec.rb +158 -0
  33. data/spec/schedule_cron_spec.rb +66 -0
  34. data/spec/schedule_every_spec.rb +109 -0
  35. data/spec/schedule_in_spec.rb +80 -0
  36. data/spec/schedule_interval_spec.rb +128 -0
  37. data/spec/scheduler_spec.rb +928 -124
  38. data/spec/spec_helper.rb +126 -0
  39. data/spec/threads_spec.rb +96 -0
  40. data/spec/zotime_spec.rb +396 -0
  41. metadata +56 -33
  42. data/README.rdoc +0 -661
  43. data/lib/rufus/otime.rb +0 -3
  44. data/lib/rufus/sc/jobqueues.rb +0 -160
  45. data/lib/rufus/sc/jobs.rb +0 -471
  46. data/lib/rufus/sc/rtime.rb +0 -363
  47. data/lib/rufus/sc/scheduler.rb +0 -636
  48. data/lib/rufus/sc/version.rb +0 -32
  49. data/spec/at_in_spec.rb +0 -47
  50. data/spec/at_spec.rb +0 -125
  51. data/spec/blocking_spec.rb +0 -64
  52. data/spec/cron_spec.rb +0 -134
  53. data/spec/every_spec.rb +0 -304
  54. data/spec/exception_spec.rb +0 -113
  55. data/spec/in_spec.rb +0 -150
  56. data/spec/mutex_spec.rb +0 -159
  57. data/spec/rtime_spec.rb +0 -137
  58. data/spec/schedulable_spec.rb +0 -97
  59. data/spec/spec_base.rb +0 -87
  60. data/spec/stress_schedule_unschedule_spec.rb +0 -159
  61. data/spec/timeout_spec.rb +0 -148
  62. data/test/kjw.rb +0 -113
  63. data/test/t.rb +0 -20
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2006-2013, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2006-2015, John Mettraux, jmettraux@gmail.com
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  # of this software and associated documentation files (the "Software"), to deal
@@ -22,41 +22,622 @@
22
22
  # Made in Japan.
23
23
  #++
24
24
 
25
+ require 'date' if RUBY_VERSION < '1.9.0'
26
+ require 'time'
27
+ require 'thread'
28
+ #require 'tzinfo'
25
29
 
26
- require 'rufus/sc/scheduler'
27
30
 
31
+ module Rufus
28
32
 
29
- module Rufus::Scheduler
33
+ class Scheduler
30
34
 
31
- # Starts and return a new instance of a PlainScheduler.
32
- #
33
- def self.new(opts={})
35
+ require 'rufus/scheduler/util'
36
+ require 'rufus/scheduler/zotime'
37
+ require 'rufus/scheduler/jobs'
38
+ require 'rufus/scheduler/cronline'
39
+ require 'rufus/scheduler/job_array'
40
+ require 'rufus/scheduler/locks'
34
41
 
35
- PlainScheduler.start_new(opts)
36
- end
42
+ VERSION = '3.1.0'
43
+
44
+ #
45
+ # A common error class for rufus-scheduler
46
+ #
47
+ class Error < StandardError; end
48
+
49
+ #
50
+ # This error is thrown when the :timeout attribute triggers
51
+ #
52
+ class TimeoutError < Error; end
53
+
54
+ #
55
+ # For when the scheduler is not running
56
+ # (it got shut down or didn't start because of a lock)
57
+ #
58
+ class NotRunningError < Error; end
59
+
60
+ #MIN_WORK_THREADS = 3
61
+ MAX_WORK_THREADS = 28
62
+
63
+ attr_accessor :frequency
64
+ attr_reader :started_at
65
+ attr_reader :thread
66
+ attr_reader :thread_key
67
+ attr_reader :mutexes
68
+
69
+ #attr_accessor :min_work_threads
70
+ attr_accessor :max_work_threads
71
+
72
+ attr_accessor :stderr
73
+
74
+ attr_reader :work_queue
75
+
76
+ def initialize(opts={})
77
+
78
+ @opts = opts
79
+
80
+ @started_at = nil
81
+ @paused = false
82
+
83
+ @jobs = JobArray.new
84
+
85
+ @frequency = Rufus::Scheduler.parse(opts[:frequency] || 0.300)
86
+ @mutexes = {}
37
87
 
38
- # A quick way to get a scheduler up an running
39
- #
40
- # require 'rubygems'
41
- # s = Rufus::Scheduler.start_new
42
- #
43
- # If EventMachine is present and running will create an EmScheduler, else
44
- # it will create a PlainScheduler instance.
45
- #
46
- def self.start_new(opts={})
47
-
48
- if defined?(EM) and EM.reactor_running?
49
- EmScheduler.start_new(opts)
50
- else
51
- PlainScheduler.start_new(opts)
88
+ @work_queue = Queue.new
89
+
90
+ #@min_work_threads = opts[:min_work_threads] || MIN_WORK_THREADS
91
+ @max_work_threads = opts[:max_work_threads] || MAX_WORK_THREADS
92
+
93
+ @stderr = $stderr
94
+
95
+ @thread_key = "rufus_scheduler_#{self.object_id}"
96
+
97
+ @scheduler_lock =
98
+ if lockfile = opts[:lockfile]
99
+ Rufus::Scheduler::FileLock.new(lockfile)
100
+ else
101
+ opts[:scheduler_lock] || Rufus::Scheduler::NullLock.new
102
+ end
103
+
104
+ @trigger_lock = opts[:trigger_lock] || Rufus::Scheduler::NullLock.new
105
+
106
+ # If we can't grab the @scheduler_lock, don't run.
107
+ @scheduler_lock.lock || return
108
+
109
+ start
52
110
  end
53
- end
54
111
 
55
- # Returns true if the given string seems to be a cron string.
56
- #
57
- def self.is_cron_string(s)
112
+ # Returns a singleton Rufus::Scheduler instance
113
+ #
114
+ def self.singleton(opts={})
115
+
116
+ @singleton ||= Rufus::Scheduler.new(opts)
117
+ end
118
+
119
+ # Alias for Rufus::Scheduler.singleton
120
+ #
121
+ def self.s(opts={}); singleton(opts); end
122
+
123
+ # Releasing the gem would probably require redirecting .start_new to
124
+ # .new and emit a simple deprecation message.
125
+ #
126
+ # For now, let's assume the people pointing at rufus-scheduler/master
127
+ # on GitHub know what they do...
128
+ #
129
+ def self.start_new
130
+
131
+ fail "this is rufus-scheduler 3.0, use .new instead of .start_new"
132
+ end
133
+
134
+ def shutdown(opt=nil)
135
+
136
+ @started_at = nil
137
+
138
+ #jobs.each { |j| j.unschedule }
139
+ # provokes https://github.com/jmettraux/rufus-scheduler/issue/98
140
+ @jobs.array.each { |j| j.unschedule }
141
+
142
+ @work_queue.clear
143
+
144
+ if opt == :wait
145
+ join_all_work_threads
146
+ elsif opt == :kill
147
+ kill_all_work_threads
148
+ end
149
+
150
+ unlock
151
+ end
152
+
153
+ alias stop shutdown
154
+
155
+ def uptime
156
+
157
+ @started_at ? Time.now - @started_at : nil
158
+ end
159
+
160
+ def uptime_s
161
+
162
+ self.class.to_duration(uptime)
163
+ end
164
+
165
+ def join
166
+
167
+ fail NotRunningError.new(
168
+ 'cannot join scheduler that is not running'
169
+ ) unless @thread
170
+
171
+ @thread.join
172
+ end
173
+
174
+ def down?
175
+
176
+ ! @started_at
177
+ end
178
+
179
+ def up?
180
+
181
+ !! @started_at
182
+ end
183
+
184
+ def paused?
185
+
186
+ @paused
187
+ end
188
+
189
+ def pause
190
+
191
+ @paused = true
192
+ end
193
+
194
+ def resume
195
+
196
+ @paused = false
197
+ end
198
+
199
+ #--
200
+ # scheduling methods
201
+ #++
202
+
203
+ def at(time, callable=nil, opts={}, &block)
204
+
205
+ do_schedule(:once, time, callable, opts, opts[:job], block)
206
+ end
207
+
208
+ def schedule_at(time, callable=nil, opts={}, &block)
209
+
210
+ do_schedule(:once, time, callable, opts, true, block)
211
+ end
212
+
213
+ def in(duration, callable=nil, opts={}, &block)
214
+
215
+ do_schedule(:once, duration, callable, opts, opts[:job], block)
216
+ end
217
+
218
+ def schedule_in(duration, callable=nil, opts={}, &block)
219
+
220
+ do_schedule(:once, duration, callable, opts, true, block)
221
+ end
222
+
223
+ def every(duration, callable=nil, opts={}, &block)
224
+
225
+ do_schedule(:every, duration, callable, opts, opts[:job], block)
226
+ end
227
+
228
+ def schedule_every(duration, callable=nil, opts={}, &block)
229
+
230
+ do_schedule(:every, duration, callable, opts, true, block)
231
+ end
232
+
233
+ def interval(duration, callable=nil, opts={}, &block)
234
+
235
+ do_schedule(:interval, duration, callable, opts, opts[:job], block)
236
+ end
237
+
238
+ def schedule_interval(duration, callable=nil, opts={}, &block)
239
+
240
+ do_schedule(:interval, duration, callable, opts, true, block)
241
+ end
242
+
243
+ def cron(cronline, callable=nil, opts={}, &block)
244
+
245
+ do_schedule(:cron, cronline, callable, opts, opts[:job], block)
246
+ end
247
+
248
+ def schedule_cron(cronline, callable=nil, opts={}, &block)
249
+
250
+ do_schedule(:cron, cronline, callable, opts, true, block)
251
+ end
252
+
253
+ def schedule(arg, callable=nil, opts={}, &block)
254
+
255
+ opts[:_t] = Scheduler.parse(arg, opts)
256
+
257
+ case opts[:_t]
258
+ when CronLine then schedule_cron(arg, callable, opts, &block)
259
+ when Time then schedule_at(arg, callable, opts, &block)
260
+ else schedule_in(arg, callable, opts, &block)
261
+ end
262
+ end
263
+
264
+ def repeat(arg, callable=nil, opts={}, &block)
265
+
266
+ opts[:_t] = Scheduler.parse(arg, opts)
267
+
268
+ case opts[:_t]
269
+ when CronLine then schedule_cron(arg, callable, opts, &block)
270
+ else schedule_every(arg, callable, opts, &block)
271
+ end
272
+ end
273
+
274
+ def unschedule(job_or_job_id)
275
+
276
+ job, job_id = fetch(job_or_job_id)
277
+
278
+ fail ArgumentError.new("no job found with id '#{job_id}'") unless job
279
+
280
+ job.unschedule if job
281
+ end
282
+
283
+ #--
284
+ # jobs methods
285
+ #++
286
+
287
+ # Returns all the scheduled jobs
288
+ # (even those right before re-schedule).
289
+ #
290
+ def jobs(opts={})
291
+
292
+ opts = { opts => true } if opts.is_a?(Symbol)
293
+
294
+ jobs = @jobs.to_a
295
+
296
+ if opts[:running]
297
+ jobs = jobs.select { |j| j.running? }
298
+ elsif ! opts[:all]
299
+ jobs = jobs.reject { |j| j.next_time.nil? || j.unscheduled_at }
300
+ end
58
301
 
59
- s.match(/.+ .+ .+ .+ .+/) # well...
302
+ tags = Array(opts[:tag] || opts[:tags]).collect { |t| t.to_s }
303
+ jobs = jobs.reject { |j| tags.find { |t| ! j.tags.include?(t) } }
304
+
305
+ jobs
306
+ end
307
+
308
+ def at_jobs(opts={})
309
+
310
+ jobs(opts).select { |j| j.is_a?(Rufus::Scheduler::AtJob) }
311
+ end
312
+
313
+ def in_jobs(opts={})
314
+
315
+ jobs(opts).select { |j| j.is_a?(Rufus::Scheduler::InJob) }
316
+ end
317
+
318
+ def every_jobs(opts={})
319
+
320
+ jobs(opts).select { |j| j.is_a?(Rufus::Scheduler::EveryJob) }
321
+ end
322
+
323
+ def interval_jobs(opts={})
324
+
325
+ jobs(opts).select { |j| j.is_a?(Rufus::Scheduler::IntervalJob) }
326
+ end
327
+
328
+ def cron_jobs(opts={})
329
+
330
+ jobs(opts).select { |j| j.is_a?(Rufus::Scheduler::CronJob) }
331
+ end
332
+
333
+ def job(job_id)
334
+
335
+ @jobs[job_id]
336
+ end
337
+
338
+ # Returns true if the scheduler has acquired the [exclusive] lock and
339
+ # thus may run.
340
+ #
341
+ # Most of the time, a scheduler is run alone and this method should
342
+ # return true. It is useful in cases where among a group of applications
343
+ # only one of them should run the scheduler. For schedulers that should
344
+ # not run, the method should return false.
345
+ #
346
+ # Out of the box, rufus-scheduler proposes the
347
+ # :lockfile => 'path/to/lock/file' scheduler start option. It makes
348
+ # it easy for schedulers on the same machine to determine which should
349
+ # run (the first to write the lockfile and lock it). It uses "man 2 flock"
350
+ # so it probably won't work reliably on distributed file systems.
351
+ #
352
+ # If one needs to use a special/different locking mechanism, the scheduler
353
+ # accepts :scheduler_lock => lock_object. lock_object only needs to respond
354
+ # to #lock
355
+ # and #unlock, and both of these methods should be idempotent.
356
+ #
357
+ # Look at rufus/scheduler/locks.rb for an example.
358
+ #
359
+ def lock
360
+
361
+ @scheduler_lock.lock
362
+ end
363
+
364
+ # Sister method to #lock, is called when the scheduler shuts down.
365
+ #
366
+ def unlock
367
+
368
+ @trigger_lock.unlock
369
+ @scheduler_lock.unlock
370
+ end
371
+
372
+ # Callback called when a job is triggered. If the lock cannot be acquired,
373
+ # the job won't run (though it'll still be scheduled to run again if
374
+ # necessary).
375
+ #
376
+ def confirm_lock
377
+
378
+ @trigger_lock.lock
379
+ end
380
+
381
+ # Returns true if this job is currently scheduled.
382
+ #
383
+ # Takes extra care to answer true if the job is a repeat job
384
+ # currently firing.
385
+ #
386
+ def scheduled?(job_or_job_id)
387
+
388
+ job, job_id = fetch(job_or_job_id)
389
+
390
+ !! (job && job.next_time != nil)
391
+ end
392
+
393
+ # Lists all the threads associated with this scheduler.
394
+ #
395
+ def threads
396
+
397
+ Thread.list.select { |t| t[thread_key] }
398
+ end
399
+
400
+ # Lists all the work threads (the ones actually running the scheduled
401
+ # block code)
402
+ #
403
+ # Accepts a query option, which can be set to:
404
+ # * :all (default), returns all the threads that are work threads
405
+ # or are currently running a job
406
+ # * :active, returns all threads that are currenly running a job
407
+ # * :vacant, returns the threads that are not running a job
408
+ #
409
+ # If, thanks to :blocking => true, a job is scheduled to monopolize the
410
+ # main scheduler thread, that thread will get returned when :active or
411
+ # :all.
412
+ #
413
+ def work_threads(query=:all)
414
+
415
+ ts =
416
+ threads.select { |t|
417
+ t[:rufus_scheduler_job] || t[:rufus_scheduler_work_thread]
418
+ }
419
+
420
+ case query
421
+ when :active then ts.select { |t| t[:rufus_scheduler_job] }
422
+ when :vacant then ts.reject { |t| t[:rufus_scheduler_job] }
423
+ else ts
424
+ end
425
+ end
426
+
427
+ def running_jobs(opts={})
428
+
429
+ jobs(opts.merge(:running => true))
430
+ end
431
+
432
+ def occurrences(time0, time1, format=:per_job)
433
+
434
+ h = {}
435
+
436
+ jobs.each do |j|
437
+ os = j.occurrences(time0, time1)
438
+ h[j] = os if os.any?
439
+ end
440
+
441
+ if format == :timeline
442
+ a = []
443
+ h.each { |j, ts| ts.each { |t| a << [ t, j ] } }
444
+ a.sort_by { |(t, j)| t }
445
+ else
446
+ h
447
+ end
448
+ end
449
+
450
+ def timeline(time0, time1)
451
+
452
+ occurrences(time0, time1, :timeline)
453
+ end
454
+
455
+ def on_error(job, err)
456
+
457
+ pre = err.object_id.to_s
458
+
459
+ ms = {}; mutexes.each { |k, v| ms[k] = v.locked? }
460
+
461
+ stderr.puts("{ #{pre} rufus-scheduler intercepted an error:")
462
+ stderr.puts(" #{pre} job:")
463
+ stderr.puts(" #{pre} #{job.class} #{job.original.inspect} #{job.opts.inspect}")
464
+ # TODO: eventually use a Job#detail or something like that
465
+ stderr.puts(" #{pre} error:")
466
+ stderr.puts(" #{pre} #{err.object_id}")
467
+ stderr.puts(" #{pre} #{err.class}")
468
+ stderr.puts(" #{pre} #{err}")
469
+ err.backtrace.each do |l|
470
+ stderr.puts(" #{pre} #{l}")
471
+ end
472
+ stderr.puts(" #{pre} tz:")
473
+ stderr.puts(" #{pre} ENV['TZ']: #{ENV['TZ']}")
474
+ stderr.puts(" #{pre} Time.now: #{Time.now}")
475
+ stderr.puts(" #{pre} scheduler:")
476
+ stderr.puts(" #{pre} object_id: #{object_id}")
477
+ stderr.puts(" #{pre} opts:")
478
+ stderr.puts(" #{pre} #{@opts.inspect}")
479
+ stderr.puts(" #{pre} frequency: #{self.frequency}")
480
+ stderr.puts(" #{pre} scheduler_lock: #{@scheduler_lock.inspect}")
481
+ stderr.puts(" #{pre} trigger_lock: #{@trigger_lock.inspect}")
482
+ stderr.puts(" #{pre} uptime: #{uptime} (#{uptime_s})")
483
+ stderr.puts(" #{pre} down?: #{down?}")
484
+ stderr.puts(" #{pre} threads: #{self.threads.size}")
485
+ stderr.puts(" #{pre} thread: #{self.thread}")
486
+ stderr.puts(" #{pre} thread_key: #{self.thread_key}")
487
+ stderr.puts(" #{pre} work_threads: #{work_threads.size}")
488
+ stderr.puts(" #{pre} active: #{work_threads(:active).size}")
489
+ stderr.puts(" #{pre} vacant: #{work_threads(:vacant).size}")
490
+ stderr.puts(" #{pre} max_work_threads: #{max_work_threads}")
491
+ stderr.puts(" #{pre} mutexes: #{ms.inspect}")
492
+ stderr.puts(" #{pre} jobs: #{jobs.size}")
493
+ stderr.puts(" #{pre} at_jobs: #{at_jobs.size}")
494
+ stderr.puts(" #{pre} in_jobs: #{in_jobs.size}")
495
+ stderr.puts(" #{pre} every_jobs: #{every_jobs.size}")
496
+ stderr.puts(" #{pre} interval_jobs: #{interval_jobs.size}")
497
+ stderr.puts(" #{pre} cron_jobs: #{cron_jobs.size}")
498
+ stderr.puts(" #{pre} running_jobs: #{running_jobs.size}")
499
+ stderr.puts(" #{pre} work_queue: #{work_queue.size}")
500
+ stderr.puts("} #{pre} .")
501
+
502
+ rescue => e
503
+
504
+ stderr.puts("failure in #on_error itself:")
505
+ stderr.puts(e.inspect)
506
+ stderr.puts(e.backtrace)
507
+
508
+ ensure
509
+
510
+ stderr.flush
511
+ end
512
+
513
+ protected
514
+
515
+ # Returns [ job, job_id ]
516
+ #
517
+ def fetch(job_or_job_id)
518
+
519
+ if job_or_job_id.respond_to?(:job_id)
520
+ [ job_or_job_id, job_or_job_id.job_id ]
521
+ else
522
+ [ job(job_or_job_id), job_or_job_id ]
523
+ end
524
+ end
525
+
526
+ def terminate_all_jobs
527
+
528
+ jobs.each { |j| j.unschedule }
529
+
530
+ sleep 0.01 while running_jobs.size > 0
531
+ end
532
+
533
+ def join_all_work_threads
534
+
535
+ work_threads.size.times { @work_queue << :sayonara }
536
+
537
+ work_threads.each { |t| t.join }
538
+
539
+ @work_queue.clear
540
+ end
541
+
542
+ def kill_all_work_threads
543
+
544
+ work_threads.each { |t| t.kill }
545
+ end
546
+
547
+ #def free_all_work_threads
548
+ #
549
+ # work_threads.each { |t| t.raise(KillSignal) }
550
+ #end
551
+
552
+ def start
553
+
554
+ @started_at = Time.now
555
+
556
+ @thread =
557
+ Thread.new do
558
+
559
+ while @started_at do
560
+
561
+ unschedule_jobs
562
+ trigger_jobs unless @paused
563
+ timeout_jobs
564
+
565
+ sleep(@frequency)
566
+ end
567
+ end
568
+
569
+ @thread[@thread_key] = true
570
+ @thread[:rufus_scheduler] = self
571
+ @thread[:name] = @opts[:thread_name] || "#{@thread_key}_scheduler"
572
+ end
573
+
574
+ def unschedule_jobs
575
+
576
+ @jobs.delete_unscheduled
577
+ end
578
+
579
+ def trigger_jobs
580
+
581
+ now = Time.now
582
+
583
+ @jobs.each(now) do |job|
584
+
585
+ job.trigger(now)
586
+ end
587
+ end
588
+
589
+ def timeout_jobs
590
+
591
+ work_threads(:active).each do |t|
592
+
593
+ job = t[:rufus_scheduler_job]
594
+ to = t[:rufus_scheduler_timeout]
595
+
596
+ next unless job && to
597
+ # thread might just have become inactive (job -> nil)
598
+
599
+ ts = t[:rufus_scheduler_time]
600
+ to = to.is_a?(Time) ? to : ts + to
601
+
602
+ next if to > Time.now
603
+
604
+ t.raise(Rufus::Scheduler::TimeoutError)
605
+ end
606
+ end
607
+
608
+ def do_schedule(job_type, t, callable, opts, return_job_instance, block)
609
+
610
+ fail NotRunningError.new(
611
+ 'cannot schedule, scheduler is down or shutting down'
612
+ ) if @started_at == nil
613
+
614
+ callable, opts = nil, callable if callable.is_a?(Hash)
615
+ return_job_instance ||= opts[:job]
616
+
617
+ job_class =
618
+ case job_type
619
+ when :once
620
+ opts[:_t] ||= Rufus::Scheduler.parse(t, opts)
621
+ opts[:_t].is_a?(Time) ? AtJob : InJob
622
+ when :every
623
+ EveryJob
624
+ when :interval
625
+ IntervalJob
626
+ when :cron
627
+ CronJob
628
+ end
629
+
630
+ job = job_class.new(self, t, opts, block || callable)
631
+
632
+ raise ArgumentError.new(
633
+ "job frequency (#{job.frequency}) is higher than " +
634
+ "scheduler frequency (#{@frequency})"
635
+ ) if job.respond_to?(:frequency) && job.frequency < @frequency
636
+
637
+ @jobs.push(job)
638
+
639
+ return_job_instance ? job : job.job_id
640
+ end
60
641
  end
61
642
  end
62
643
 
@@ -4,7 +4,7 @@ Gem::Specification.new do |s|
4
4
  s.name = 'rufus-scheduler'
5
5
 
6
6
  s.version = File.read(
7
- File.expand_path('../lib/rufus/sc/version.rb', __FILE__)
7
+ File.expand_path('../lib/rufus/scheduler.rb', __FILE__)
8
8
  ).match(/ VERSION *= *['"]([^'"]+)/)[1]
9
9
 
10
10
  s.platform = Gem::Platform::RUBY
@@ -12,8 +12,8 @@ Gem::Specification.new do |s|
12
12
  s.email = [ 'jmettraux@gmail.com' ]
13
13
  s.homepage = 'http://github.com/jmettraux/rufus-scheduler'
14
14
  s.rubyforge_project = 'rufus'
15
- s.summary = 'job scheduler for Ruby (at, cron, in and every jobs)'
16
15
  s.license = 'MIT'
16
+ s.summary = 'job scheduler for Ruby (at, cron, in and every jobs)'
17
17
 
18
18
  s.description = %{
19
19
  job scheduler for Ruby (at, cron, in and every jobs).
@@ -26,10 +26,12 @@ job scheduler for Ruby (at, cron, in and every jobs).
26
26
  '*.gemspec', '*.txt', '*.rdoc', '*.md'
27
27
  ]
28
28
 
29
- s.add_runtime_dependency 'tzinfo', '>= 0.3.22'
29
+ #s.add_runtime_dependency 'tzinfo'
30
30
 
31
31
  s.add_development_dependency 'rake'
32
- s.add_development_dependency 'rspec', '>= 2.7.0'
32
+ s.add_development_dependency 'rspec', '>= 2.13.0'
33
+ s.add_development_dependency 'chronic'
34
+ s.add_development_dependency 'tzinfo'
33
35
 
34
36
  s.require_path = 'lib'
35
37
  end