package Yote::Cron;
#######################################################################################################
# This module is not meant to be used directly. It is activated automatically as part of the service. #
#######################################################################################################
use strict;
use warnings;
no warnings 'uninitialized';
use vars qw($VERSION);
$VERSION = '0.015';
use DateTime;
use parent 'Yote::RootObj';
##################
# Public Methods #
##################
sub add_entry {
my( $self, $entry, $acct ) = @_;
# TODO - enforce unique name to entries
$self->add_to_entries( $entry );
$self->_update_entry( $entry );
return $entry;
} #add_entry
sub entries {
my $self = shift;
my $now_running = _time();
my $entries = $self->get_entries([]);
return [grep { $_->get_enabled() && $_->get_next_time() && $now_running >= $_->get_next_time() } @$entries ];
} #entries
sub mark_done {
my( $self, $entry, $acct ) = @_;
die "Access Error" unless $acct->is_root();
return $self->_mark_done( $entry );
}
# starts a cron thread. All this does is queues up a message in the engine to check the cron.
sub start {
my $cfg = shift;
while( 1 ) {
my $sock = new IO::Socket::INET( "127.0.0.1:$cfg->{internal_port}" );
print $sock "CRON";
close $sock;
sleep 50;
}
} #start
sub prefetch {
my( $self, $data, $acct ) = @_;
if( $acct && $acct->is_root() ) {
return [ $self->get_entries(),
( map { $_,
@{$_->get_repeats([])},
$_->get_repeats(),
@{$_->get_scheduled_times([])},
$_->get_scheduled_times() }
@{ $self->get_entries([]) } )
];
}
}
sub check {
my $cron = shift;
my $cron_entries = $cron->entries();
# TODO : make sure nothing volatile is here
# ALSO TODO : for security sake, only allow this to call a yote method?
for my $entry (@$cron_entries) {
# TODO : Just queue up a cron in the execution engine
$cron->_mark_done( $entry );
my $script = $entry->get_script();
print STDERR "EVAL $script\n";
eval "$script";
print STDERR "Done EVAL\n";
if( $@ ) {
print STDERR "Error in Cron : $@ $!\n";
}
} #each cron entry
} #check
sub update_entry {
my( $self, $entry, $acct ) = @_;
return $self->_update_entry( $entry );
} #update_entry
###################
# Private Methods #
###################
# cron entries will have the following fields :
# * script - what to run
# * repeats - a list of hashes with the following values :
# * repeat_infinite - true if this will always be repeated
# * repeat_times - a number of times to repeat this; this decrements
# * repeat_interval - how many seconds to repeat this
# * scheduled_times - a list of epoc times this cron should be run
# * next_time - epoc time this should be run next
# * last_run - time this was last run
sub _init {
my $self = shift;
my $first_cron = new Yote::RootObj( {
name => 'recycler',
enabled => 1,
script => 'use Data::Dumper; my $recycled = Yote::ObjProvider::recycle_objects(); print STDERR Data::Dumper->Dump(["Recycled $recycled Objects"]);',
repeats => [
new Yote::Obj( { repeat_interval => 50, repeat_infinite => 1, repeat_times => 0 } ),
],
} );
$self->add_entry( $first_cron );
my $second_cron = new Yote::RootObj( {
name => 'Token Janitor',
enabled => 1,
script => 'use Data::Dumper; my $dumped = Yote::Root::fetch_root()->_clear_old_tokens(); print STDERR Data::Dumper->Dump(["Dumped $dumped old Tokens"]);',
repeats => [
new Yote::Obj( { repeat_interval => 30, repeat_infinite => 1, repeat_times => 0 } ),
],
} );
$self->add_entry( $second_cron );
} #_init
sub _mark_done {
my( $self, $entry ) = @_;
my $ran_at = _time();
my $next_time;
$entry->set_last_run( $ran_at );
my $repeats = $entry->get_repeats();
if( $repeats ) {
my( @repeats ) = @$repeats;
for( my $i=$#repeats; $i>=0; $i-- ) {
my $rep = $repeats[$i];
if( $rep->get_next_time() <= $ran_at ) {
if( $rep->get_repeat_infinite() ) {
$rep->set_next_time( $rep->get_next_time() + $rep->get_repeat_interval() );
$rep->set_next_time( $ran_at + $rep->get_repeat_interval() ) if $rep->get_next_time() <= $ran_at;
}
elsif( $rep->get_next_time() <= $ran_at ) {
$rep->set_repeat_times( $rep->get_repeat_times() - 1 );
if( $rep->get_repeat_times() > 0 ) {
$rep->set_next_time( $ran_at + $rep->get_repeat_interval() );
}
else {
splice @$repeats, $i, 1;
$rep->set_next_time( 0 );
}
}
}
$next_time = $rep->get_next_time() && $next_time >= $rep->get_next_time() ? $next_time : $rep->get_next_time();
}
} #if repeats
my $times = $entry->get_scheduled_times();
if( $times ) {
my( @times ) = @$times;
for( my $i=$#times; $i>=0; $i-- ) {
my $sched = $times[$i];
if( $sched <= $ran_at ) {
splice @$times, $i, 1;
}
elsif( $sched > $ran_at ) {
$next_time = $sched < $next_time ? $sched : $next_time;
}
}
}
$entry->set_next_time( $next_time );
unless( $next_time ) {
$entry->set_enabled( 0 );
}
} #_mark_done
#
# Time is moved to its own sub in order to allow for modification for testing.
# Returns time in minutes
#
sub _time {
return time/60;
} #_time
sub _update_entry {
my( $self, $entry ) = @_;
my $repeats = $entry->get_repeats();
my $added_on = _time();
my $next_time;
if( $repeats ) {
for my $rep (@$repeats) {
next unless $rep->get_repeat_infinite() || $rep->get_repeat_times();
$rep->set_next_time( $added_on + $rep->get_repeat_interval() );
$next_time ||= $rep->get_next_time();
$next_time = $rep->get_next_time() if $next_time > $rep->get_next_time();
}
} #if repeats
my $times = $entry->get_scheduled_times();
if( $times ) {
for( my $i=$#$times; $i >= 0; $i-- ) {
splice( @$times, $i, 1 ) unless $times->[$i] > $added_on;
}
for my $sched ( @$times ) {
$next_time ||= ( $added_on + $sched );
$next_time = $sched if $sched < $next_time;
}
}
$entry->set_next_time( $next_time );
return $entry;
} #_update_entry
1;
__END__
=head1 NAME
Yote::Cron
=head1 SYNOPSIS
Yote::Cron is a subsystem in Yote that functions like a cron, allowing scripts to be run inside Yote. Rather than use
config files, this uses Yote objects to control the cron jobs. A cron editor is part of the Yote admin page.
=head1 DESCRIPTION
The Yote::Cron is set up on the yote system and runs every minute, checking if it should run
any method that is attached to a yote object. It is a limited version of a cron system, as it
for now only registers methods with minutes and hours.
The Yote::Cron's public methods can only be called by an account with the __is_root flag set.
=head1 PUBLIC METHODS
=over 4
=item add_entry( $entry )
Ads an entry to this list. Takes a Yote::Obj that has the following data structure :
* name - name of script to run
* enabled - if true, this cron is active
* script - what to run
* repeats - a list of hashes with the following values :
* repeat_infinite - true if this will always be repeated
* repeat_times - a number of times to repeat this; this decrements
* repeat_interval - how many seconds to repeat this
* scheduled times - a list of epoc times this cron should be run
* next_time - epoc time this should be run next (volatile, should not be set by user)
* last_run - time this was last run (volatile, should not be set by user)
=item entries()
Returns a list of the entries that should be run at the time this was called.
=item mark_done( $entry )
Marks this entry as done. This causes any repeat_times to decrement, and removes appropriate scheduled times.
=item start( $entry )
Starts main loop for cron.
=item update_entry( $entry )
This recalculates the next time this entry will be run.
=back
=head1 AUTHOR
Eric Wolf
coyocanid@gmail.com
http://madyote.com
=head1 LICENSE AND COPYRIGHT
Copyright (C) 2011 Eric Wolf
This module is free software; it can be used under the same terms as perl
itself.
=cut