rufus-scheduler 2.0.24 → 3.1.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.
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