#-- # Copyright (c) 2006-2009, John Mettraux, jmettraux@gmail.com # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # Made in Japan. #++ module Rufus module Scheduler # # The base class for all types of jobs. # class Job # A reference to the scheduler owning this job # attr_accessor :scheduler # The initial, raw, scheduling info (at / in / every / cron) # attr_reader :t # When the job is actually running, this attribute will hold the # thread in which the job is running. # attr_reader :job_thread # The job parameters (passed via the schedule method) # attr_reader :params # The block to call when triggering # attr_reader :block # Last time the job executed # (for an {At|In}Job, it will mean 'not executed' if nil or when # it got executed if set) # # ( # Last time job got triggered (most useful with EveryJob, but can be # useful with remaining instances of At/InJob (are they done ?)) # ) # attr_reader :last # The identifier for this job. # attr_reader :job_id # Instantiating the job. # def initialize (scheduler, t, params, &block) @scheduler = scheduler @t = t @params = params @block = block || params[:schedulable] raise ArgumentError.new( 'no block or :schedulable passed, nothing to schedule' ) unless @block @params[:tags] = Array(@params[:tags]) @job_id = params[:job_id] || "#{self.class.name}_#{self.object_id.to_s}" determine_at end # Returns the list of tags attached to the job. # def tags @params[:tags] end # Sets the list of tags attached to the job (Usually they are set # via the schedule every/at/in/cron method). # def tags= (tags) @params[:tags] = Array(tags) end # Generally returns the string/float/integer used to schedule the job # (seconds, time string, date string) # def schedule_info @t end # Triggers the job. # def trigger (t=Time.now) @last = t @scheduler.send(:trigger_job, @params[:blocking]) do # # Note that #trigger_job is protected, hence the #send # (Only jobs know about this method of the scheduler) @job_thread = Thread.current begin trigger_block @job_thread = nil rescue Exception => e @scheduler.handle_exception(self, e) end end # note that add_job and add_cron_job ensured that :blocking is # not used along :timeout if to = @params[:timeout] @scheduler.in(to, :tags => 'timeout') do # at this point, @job_thread might be set @job_thread.raise(Rufus::Scheduler::TimeOutError) \ if @job_thread and @job_thread.alive? end end end # Simply encapsulating the block#call/trigger operation, for easy # override. # def trigger_block @block.respond_to?(:call) ? @block.call(self) : @block.trigger(@params.merge(:job => self)) end # Unschedules this job. # def unschedule @scheduler.unschedule(self.job_id) end #-- #protected # # Prepare the args given the triggered block arity. # #def prepare_args # if @scheduler.options[:onezero_block_arity] # case @block.arity # when 0 then [] # when 1 then [ @params ] # when 2 then [ @job_id, @params ] # #else [ @job_id, schedule_info, @params ] # else [ @job_id, self, @params ] # end # else # [ self ] # end #end #++ end # # The base class of at/in/every jobs. # class SimpleJob < Job # When the job is supposed to trigger # attr_reader :at attr_reader :last def determine_at @at = Rufus.at_to_f(@t) end # Returns the next time (or the unique time) this job is meant to trigger # def next_time Time.at(@at) end end # # Job that occurs once, in a certain amount of time. # class InJob < SimpleJob protected def determine_at iin = @t.is_a?(Fixnum) || @t.is_a?(Float) ? @t : Rufus.parse_duration_string(@t) @at = (Time.now + iin).to_f end end # # Job that occurs once, at a certain point in time. # class AtJob < SimpleJob end # # Recurring job with a certain frequency. # class EveryJob < SimpleJob # The frequency, in seconds, of this EveryJob # attr_reader :frequency def initialize (scheduler, t, params, &block) super determine_frequency determine_at end # Triggers the job (and reschedules it). # def trigger schedule_next super #unschedule if @params[:dont_reschedule] # obsolete end protected def determine_frequency @frequency = @t.is_a?(Fixnum) || @t.is_a?(Float) ? @t : Rufus.parse_duration_string(@t) end def determine_at return unless @frequency @last = @at # the first time, @last will be nil @at = if @last @last + @frequency else if fi = @params[:first_in] Time.now.to_f + Rufus.duration_to_f(fi) elsif fa = @params[:first_at] Rufus.at_to_f(fa) else Time.now.to_f + @frequency end end end # It's an every job, have to schedule next time it occurs... # def schedule_next determine_at @scheduler.send(:add_job, self) end end # # Recurring job, cron style. # class CronJob < Job # The CronLine instance, it holds all the info about the cron schedule # attr_reader :cron_line # The job parameters (passed via the schedule method) # attr_reader :params # The block to call when triggering # attr_reader :block # Creates a new CronJob instance. # def initialize (scheduler, cron_string, params, &block) super @cron_line = case @t when String then CronLine.new(@t) when CronLine then @t else raise "cannot initialize a CronJob out of #{@t.inspect}" end end def trigger_if_matches (time) trigger(time) if @cron_line.matches?(time) end # Returns the next time this job is meant to trigger # def next_time (from=Time.now) @cron_line.next_time(from) end protected def determine_at # empty end end end end