# # Short help/usage: # /jobadd hour minute day_of_month month day_of_week command # Possibile switches for jobadd: # -disabled # -server # - # /jobs [-v] # /jobdel [-finished] | job_number # /jobdisable job_number # /jobenable job_number # /jobssave # /jobsload # # Examples of usage: # /jobadd 17 45 * * * /echo This will be executed at 17:45 # /jobadd -5 17 45 * * * /echo The same as above but only 5 times # /jobadd * 05 * * * /echo Execute this every hour 5 minutes after the hour # /jobadd */6 0 * * * /echo Execute at 0:0, 6:0, 12:0, 18:0 # /jobadd * */30,45 * * * /echo Execute every hour at 00, 30, 45 minute # /jobadd * 1-15/5 * * * /echo at 1,6,11 # # The servertag in -server usually is name from /ircnet, but # should work with servers not in any ircnet (hmm probably) # # The format was taken from crontab(5). # The only differences are: # 1) hour field is before minute field (why the hell minute is first in # crontab?). But this could be changed in final version. # 2) day of week is 0..6. 0 is Sunday, 1 is Monday, 6 is Saturday. # 7 is illegal value while in crontab it's the same as 0 (i.e. Sunday). # I might change this, depends on demand. # 3) you can't use names in month and day of week. You must use numbers # Type 'man 5 crontab' to know more about allowed values etc. # # TODO: # - add full (or almost full) cron functionality # - probably more efficient checking for job in timeout # - imput data validation # ? should we remember if the server was given with -server # # Changelog: # 0.12 (2014.11.12) # Automatically load jobs when loaded # # 0.11 (2004.12.12) # Job are executed exactly at the time (+- 1s), not up to 59s late # # 0.10 (2003.03.25): # Added - to execute job only times. Initial patch from # Marian Schubert (M dot Schubert at sh dot cvut dot cz) # # 0.9: # Bugfix: according to crontab(5) when both DoM and DoW are restricted # it's enough to only one of fields to match # # 0.8: # Added -disabled to /jobadd # Added jobs loading and saving to file # # 0.7: # Bugfixes. Should work now ;) # # 0.6: # Added month, day of month, day of week # # 0.5: # Initial testing release # use Irssi; use strict; use vars qw($VERSION %IRSSI); $VERSION = "0.12"; %IRSSI = ( authors => 'Piotr Krukowiecki', contact => 'piotr \at/ krukowiecki /dot\ net', name => 'cron aka jobs', description => 'cron implementation, allows to execute commands at given interval/time', license => 'GNU GPLv2', changed => '2004.12.12', url => 'http://www.krukowiecki.net/code/irssi/' ); my @jobs = (); my $seconds = (gmtime(time()))[0]; my $timeout_tag; my $stop_timeout_tag; if ($seconds > 0) { $stop_timeout_tag = Irssi::timeout_add((60-$seconds)*1000, sub { Irssi::timeout_remove($stop_timeout_tag); $timeout_tag = Irssi::timeout_add(60000, 'sig_timeout', undef); }, undef); } else { $timeout_tag = Irssi::timeout_add(60000, 'sig_timeout', undef); } my $savefile = Irssi::get_irssi_dir() . "/cron.save"; # First arg - current hour or minute. # Second arg - hour or minute specyfications. sub time_matches($$) { my ($current, $spec) = @_; foreach my $h (split(/,/, $spec)) { if ($h =~ /(.*)\/(\d+)/) { # */number or number-number/number my $step = $2; if ($1 eq '*') { # */number return 1 if ($current % $step == 0); next; } if ($1 =~ /(\d+)-(\d+)/) { # number-number/number my ($from, $to) = ($1, $2); next if ($current < $from or $current > $to); # not in range my $current = $current; if ($from > 0) { # shift time $to -= $from; $current -= $from; $from = 0; } return 1 if ($current % $step == 0); next; } next; } if ($h =~ /(\d+)-(\d+)/) { # number-number return 1 if ($current >= $1 and $current <= $2); next } return 1 if ($h eq '*' or $h == $current); # '*' or exact hour } return 0; } sub sig_timeout { my $ctime = time(); my ($cminute, $chour, $cdom, $cmonth, $cdow) = (localtime($ctime))[1,2,3,4,6]; $cmonth += 1; foreach my $job (@jobs) { next if ($job->{'disabled'}); next if ($job->{'repeats'} == 0); next if (not time_matches($chour, $job->{'hour'})); next if (not time_matches($cminute, $job->{'minute'})); next if (not time_matches($cmonth, $job->{'month'})); if ($job->{'dom'} ne '*' and $job->{'dow'} ne '*') { next if (not (time_matches($cdom, $job->{'dom'}) or time_matches($cdow, $job->{'dow'}))); } else { next if (not time_matches($cdom, $job->{'dom'})); next if (not time_matches($cdow, $job->{'dow'})); } my $server = Irssi::server_find_tag($job->{'server'}); if (!$server) { Irssi::print("cron.pl: could not find server '$job->{server}'"); next; } $server->command($job->{'commands'}); if ($job->{'repeats'} > 0) { $job->{'repeats'} -= 1; } } } sub cmd_jobs { my ($data, $server, $channel) = @_; my $verbose = ($data eq '-v'); Irssi::print("Current Jobs:"); foreach (0 .. $#jobs) { my $repeats = $jobs[$_]{'repeats'}; my $msg = "$_) "; if (!$verbose) { next if ($repeats == 0); $msg .= "-$repeats " if ($repeats != -1); } else { $msg .= "-$repeats " if ($repeats != -1); } $msg .= ($jobs[$_]{'disabled'}?"-disabled ":"") ."-server $jobs[$_]{server} " ."$jobs[$_]{hour} $jobs[$_]{minute} $jobs[$_]{dom} " ."$jobs[$_]{month} $jobs[$_]{dow} " ."$jobs[$_]{commands}"; Irssi::print($msg); } Irssi::print("End of List"); } # /jobdel job_number sub cmd_jobdel { my ($data, $server, $channel) = @_; if ($data eq "-finished") { foreach (reverse(0 .. $#jobs)) { if ($jobs[$_]{'repeats'} == 0) { splice(@jobs, $_, 1); Irssi::print("Removed Job #$_"); } } return; } elsif ($data !~ /\d+/ or $data < 0 or $data > $#jobs) { Irssi::print("Bad Job Number"); return; } splice(@jobs, $data, 1); Irssi::print("Removed Job #$data"); } # /jobdisable job_number sub cmd_jobdisable { my ($data, $server, $channel) = @_; if ($data < 0 || $data > $#jobs) { Irssi::print("Bad Job Number"); return; } $jobs[$data]{'disabled'} = 1; Irssi::print("Disabled job number $data"); } # /jobenable job_number sub cmd_jobenable { my ($data, $server, $channel) = @_; if ($data < 0 || $data > $#jobs) { Irssi::print("Bad Job Number"); return; } $jobs[$data]{'disabled'} = 0; Irssi::print("Enabled job number $data"); } # /jobadd [-X] [-disabled] [-server servertag] hour minute day_of_month month day_of_week command sub cmd_jobadd { my ($data, $server, $channel) = @_; $server = $server->{tag}; my $disabled = 0; my $repeats = -1; while ($data =~ /^\s*-/) { if ($data =~ s/^\s*-disabled\s+//) { $disabled = 1; next; } if ($data =~ s/^\s*-(\d+)\s+//) { $repeats = $1; next; } my $comm; ($comm, $server, $data) = split(' ', $data, 3); if ($comm ne '-server') { Irssi::print("Bad switch: '$comm'"); return; } } my ($hour, $minute, $dom, $month, $dow, $commands) = split(' ', $data, 6); push (@jobs, { 'hour' => $hour, 'minute' => $minute, 'dom' => $dom, 'month' => $month, 'dow' => $dow, 'server' => $server, 'commands' => $commands, 'disabled' => $disabled, 'repeats' => $repeats } ); Irssi::print("Job added"); } sub cmd_jobssave { if (not open (FILE, ">", $savefile)) { Irssi::print("Could not open file '$savefile': $!"); return; } foreach (0 .. $#jobs) { next if ($jobs[$_]->{'repeats'} == 0); # don't save finished jobs print FILE ($jobs[$_]->{'repeats'}>0 ? "-$jobs[$_]->{'repeats'} " : "") . ($jobs[$_]{'disabled'}?"-disabled ":"") ."-server $jobs[$_]{server} " ."$jobs[$_]{hour} $jobs[$_]{minute} $jobs[$_]{dom} " ."$jobs[$_]{month} $jobs[$_]{dow} " ."$jobs[$_]{commands}\n"; } close FILE; Irssi::print("Jobs saved"); } sub cmd_jobsload { if (not open (FILE, q{<}, $savefile)) { Irssi::print("Could not open file '$savefile': $!"); return; } @jobs = (); while () { chomp; cmd_jobadd($_, undef, undef); } close FILE; Irssi::print("Jobs loaded"); } cmd_jobsload(); Irssi::command_bind('jobs', 'cmd_jobs', 'Cron'); Irssi::command_bind('jobadd', 'cmd_jobadd', 'Cron'); Irssi::command_bind('jobdel', 'cmd_jobdel', 'Cron'); Irssi::command_bind('jobdisable', 'cmd_jobdisable', 'Cron'); Irssi::command_bind('jobenable', 'cmd_jobenable', 'Cron'); Irssi::command_bind('jobssave', 'cmd_jobssave', 'Cron'); Irssi::command_bind('jobsload', 'cmd_jobsload', 'Cron'); # vim:noexpandtab:ts=4