Symphony::Metronome::

ScheduledEvent class

A class the represents the relationship between an interval and an event.

Attributes

ds R

The sequel dataset representing this event.

event R

The parsed interval expression.

id R

The unique ID number of the scheduled event.

options R

The options hash attached to this event.

runtime R

The exact time that this event will run.

Public Class Methods

configure( config=nil )

Configurability API.

# File lib/symphony/metronome/scheduledevent.rb, line 44
def self::configure( config=nil )
        config = self.defaults.merge( config || {} )
        @db    = Sequel.connect( config.delete(:db) )
        @splay = config.delete( :splay )

        # Ensure the database is current.
        #
        migrations_dir = Symphony::Metronome::DATADIR + 'migrations'
        unless Sequel::Migrator.is_current?( self.db, migrations_dir.to_s )
                Loggability[ Symphony ].info "Installing database schema..."
                Sequel::Migrator.apply( self.db, migrations_dir.to_s )
        end
end
load()

Return a set of all known events, sorted by date of execution. Delete any rows that are invalid expressions.

# File lib/symphony/metronome/scheduledevent.rb, line 62
def self::load
        events = SortedSet.new

        # Force reset the DB handle.
        self.db.disconnect

        self.log.debug "Parsing/loading all actions."
        self.db[ :metronome ].each do |event|
                begin
                        event = new( event )
                        events << event
                rescue ArgumentError, Symphony::Metronome::TimeParseError => err
                        self.log.error "%p while parsing \"%s\": %s" % [
                                err.class,
                                event[:expression],
                                err.message
                        ]
                        self.log.debug "  " + err.backtrace.join( "\n  " )
                        self.db[ :metronome ].filter( :id => event[:id] ).delete
                end
        end

        return events
end
new( row )

Create a new ScheduledEvent object.

# File lib/symphony/metronome/scheduledevent.rb, line 94
def initialize( row )
        @event    = Symphony::Metronome::IntervalExpression.parse( row[:expression], row[:created] )
        @options  = row.delete( :options )
        @id       = row.delete( :id )
        @ds       = self.class.db[ :metronome ].filter( :id => self.id )

        self.reset_runtime

        unless self.class.splay.zero?
                splay = Range.new( - self.class.splay, self.class.splay )
                @runtime = self.runtime + rand( splay )
        end
end

Public Instance Methods

<=>( other )

Comparable interface, order by next run time, soonest first.

# File lib/symphony/metronome/scheduledevent.rb, line 209
def <=>( other )
        return self.runtime <=> other.runtime
end
delete()

Permanently remove this event from the database.

# File lib/symphony/metronome/scheduledevent.rb, line 201
def delete
        self.log.debug "Removing action %p" % [ self.id ]
        self.ds.delete
end
fire() { |opts, id| ... }

Perform the action attached to the event. Yields the deserialized options, the action ID to the supplied block if this event is okay to execute.

If the event is recurring, perform additional checks against the last run time.

Automatically remove the event if it has expired.

# File lib/symphony/metronome/scheduledevent.rb, line 166
def fire
        rv = self.event.fire?

        # Just based on the expression parser, is this event ready to fire?
        #
        if rv
                opts = Yajl.load( self.options )

                # Don't fire recurring events unless their interval has elapsed.
                # This prevents events from triggering when the daemon receives
                # a HUP.
                #
                if self.event.recurring
                        now = Time.now
                        row = self.ds.first

                        if row
                                last = row[ :lastrun ]
                                return false if last && now - last < self.event.interval
                        end

                        # Mark the time this recurring event was fired.
                        self.ds.update( :lastrun => Time.now )
                end

                yield opts, self.id
        end

        self.delete if rv.nil?
        return rv
end
reset_runtime()

Set the datetime that this event should fire next.

# File lib/symphony/metronome/scheduledevent.rb, line 126
def reset_runtime
        now = Time.now

        # Start time is in the future, so it's sufficent to be considered the run time.
        #
        if self.event.starting >= now
                @runtime = self.event.starting
                return
        end

        # Otherwise, the event should already be running (start time has already
        # elapsed), so schedule it forward on it's next interval iteration.
        #
        # If it's a recurring event that has run before, consider the elapsed time
        # as part of the next calculation.
        #
        row = self.ds.first
        if self.event.recurring && row
                last = row[ :lastrun ]
                if last && now > last
                        @runtime = now + self.event.interval - ( now - last )
                else
                        @runtime = now + self.event.interval
                end

        else
                @runtime = now + self.event.interval
        end
end