1# 2# Short help/usage: 3# /jobadd hour minute day_of_month month day_of_week command 4# Possibile switches for jobadd: 5# -disabled 6# -server <tag> 7# -<number> 8# /jobs [-v] 9# /jobdel [-finished] | job_number 10# /jobdisable job_number 11# /jobenable job_number 12# /jobssave 13# /jobsload 14# 15# Examples of usage: 16# /jobadd 17 45 * * * /echo This will be executed at 17:45 17# /jobadd -5 17 45 * * * /echo The same as above but only 5 times 18# /jobadd * 05 * * * /echo Execute this every hour 5 minutes after the hour 19# /jobadd */6 0 * * * /echo Execute at 0:0, 6:0, 12:0, 18:0 20# /jobadd * */30,45 * * * /echo Execute every hour at 00, 30, 45 minute 21# /jobadd * 1-15/5 * * * /echo at 1,6,11 22# 23# The servertag in -server usually is name from /ircnet, but 24# should work with servers not in any ircnet (hmm probably) 25# 26# The format was taken from crontab(5). 27# The only differences are: 28# 1) hour field is before minute field (why the hell minute is first in 29# crontab?). But this could be changed in final version. 30# 2) day of week is 0..6. 0 is Sunday, 1 is Monday, 6 is Saturday. 31# 7 is illegal value while in crontab it's the same as 0 (i.e. Sunday). 32# I might change this, depends on demand. 33# 3) you can't use names in month and day of week. You must use numbers 34# Type 'man 5 crontab' to know more about allowed values etc. 35# 36# TODO: 37# - add full (or almost full) cron functionality 38# - probably more efficient checking for job in timeout 39# - imput data validation 40# ? should we remember if the server was given with -server 41# 42# Changelog: 43# 0.12 (2014.11.12) 44# Automatically load jobs when loaded 45# 46# 0.11 (2004.12.12) 47# Job are executed exactly at the time (+- 1s), not up to 59s late 48# 49# 0.10 (2003.03.25): 50# Added -<number> to execute job only <number> times. Initial patch from 51# Marian Schubert (M dot Schubert at sh dot cvut dot cz) 52# 53# 0.9: 54# Bugfix: according to crontab(5) when both DoM and DoW are restricted 55# it's enough to only one of fields to match 56# 57# 0.8: 58# Added -disabled to /jobadd 59# Added jobs loading and saving to file 60# 61# 0.7: 62# Bugfixes. Should work now ;) 63# 64# 0.6: 65# Added month, day of month, day of week 66# 67# 0.5: 68# Initial testing release 69# 70 71use Irssi; 72use strict; 73use vars qw($VERSION %IRSSI); 74 75$VERSION = "0.12"; 76%IRSSI = ( 77 authors => 'Piotr Krukowiecki', 78 contact => 'piotr \at/ krukowiecki /dot\ net', 79 name => 'cron aka jobs', 80 description => 'cron implementation, allows to execute commands at given interval/time', 81 license => 'GNU GPLv2', 82 changed => '2004.12.12', 83 url => 'http://www.krukowiecki.net/code/irssi/' 84); 85 86my @jobs = (); 87my $seconds = (gmtime(time()))[0]; 88my $timeout_tag; 89my $stop_timeout_tag; 90if ($seconds > 0) { 91 $stop_timeout_tag = Irssi::timeout_add((60-$seconds)*1000, 92 sub { 93 Irssi::timeout_remove($stop_timeout_tag); 94 $timeout_tag = Irssi::timeout_add(60000, 'sig_timeout', undef); 95 }, undef); 96} else { 97 $timeout_tag = Irssi::timeout_add(60000, 'sig_timeout', undef); 98} 99my $savefile = Irssi::get_irssi_dir() . "/cron.save"; 100 101# First arg - current hour or minute. 102# Second arg - hour or minute specyfications. 103sub time_matches($$) { 104 my ($current, $spec) = @_; 105 foreach my $h (split(/,/, $spec)) { 106 if ($h =~ /(.*)\/(\d+)/) { # */number or number-number/number 107 my $step = $2; 108 if ($1 eq '*') { # */number 109 return 1 if ($current % $step == 0); 110 next; 111 } 112 if ($1 =~ /(\d+)-(\d+)/) { # number-number/number 113 my ($from, $to) = ($1, $2); 114 next if ($current < $from or $current > $to); # not in range 115 my $current = $current; 116 if ($from > 0) { # shift time 117 $to -= $from; 118 $current -= $from; 119 $from = 0; 120 } 121 return 1 if ($current % $step == 0); 122 next; 123 } 124 next; 125 } 126 if ($h =~ /(\d+)-(\d+)/) { # number-number 127 return 1 if ($current >= $1 and $current <= $2); 128 next 129 } 130 return 1 if ($h eq '*' or $h == $current); # '*' or exact hour 131 } 132 return 0; 133} 134 135sub sig_timeout { 136 my $ctime = time(); 137 my ($cminute, $chour, $cdom, $cmonth, $cdow) = (localtime($ctime))[1,2,3,4,6]; 138 $cmonth += 1; 139 foreach my $job (@jobs) { 140 next if ($job->{'disabled'}); 141 next if ($job->{'repeats'} == 0); 142 next if (not time_matches($chour, $job->{'hour'})); 143 next if (not time_matches($cminute, $job->{'minute'})); 144 next if (not time_matches($cmonth, $job->{'month'})); 145 if ($job->{'dom'} ne '*' and $job->{'dow'} ne '*') { 146 next if (not (time_matches($cdom, $job->{'dom'}) or 147 time_matches($cdow, $job->{'dow'}))); 148 } else { 149 next if (not time_matches($cdom, $job->{'dom'})); 150 next if (not time_matches($cdow, $job->{'dow'})); 151 } 152 153 my $server = Irssi::server_find_tag($job->{'server'}); 154 if (!$server) { 155 Irssi::print("cron.pl: could not find server '$job->{server}'"); 156 next; 157 } 158 $server->command($job->{'commands'}); 159 if ($job->{'repeats'} > 0) { 160 $job->{'repeats'} -= 1; 161 } 162 } 163} 164 165sub cmd_jobs { 166 my ($data, $server, $channel) = @_; 167 my $verbose = ($data eq '-v'); 168 Irssi::print("Current Jobs:"); 169 foreach (0 .. $#jobs) { 170 my $repeats = $jobs[$_]{'repeats'}; 171 my $msg = "$_) "; 172 if (!$verbose) { 173 next if ($repeats == 0); 174 $msg .= "-$repeats " if ($repeats != -1); 175 } else { 176 $msg .= "-$repeats " if ($repeats != -1); 177 } 178 179 $msg .= ($jobs[$_]{'disabled'}?"-disabled ":"") 180 ."-server $jobs[$_]{server} " 181 ."$jobs[$_]{hour} $jobs[$_]{minute} $jobs[$_]{dom} " 182 ."$jobs[$_]{month} $jobs[$_]{dow} " 183 ."$jobs[$_]{commands}"; 184 Irssi::print($msg); 185 } 186 Irssi::print("End of List"); 187} 188 189# /jobdel job_number 190sub cmd_jobdel { 191 my ($data, $server, $channel) = @_; 192 if ($data eq "-finished") { 193 foreach (reverse(0 .. $#jobs)) { 194 if ($jobs[$_]{'repeats'} == 0) { 195 splice(@jobs, $_, 1); 196 Irssi::print("Removed Job #$_"); 197 } 198 } 199 return; 200 } elsif ($data !~ /\d+/ or $data < 0 or $data > $#jobs) { 201 Irssi::print("Bad Job Number"); 202 return; 203 } 204 splice(@jobs, $data, 1); 205 Irssi::print("Removed Job #$data"); 206} 207 208# /jobdisable job_number 209sub cmd_jobdisable { 210 my ($data, $server, $channel) = @_; 211 if ($data < 0 || $data > $#jobs) { 212 Irssi::print("Bad Job Number"); 213 return; 214 } 215 $jobs[$data]{'disabled'} = 1; 216 Irssi::print("Disabled job number $data"); 217} 218# /jobenable job_number 219sub cmd_jobenable { 220 my ($data, $server, $channel) = @_; 221 if ($data < 0 || $data > $#jobs) { 222 Irssi::print("Bad Job Number"); 223 return; 224 } 225 $jobs[$data]{'disabled'} = 0; 226 Irssi::print("Enabled job number $data"); 227} 228 229# /jobadd [-X] [-disabled] [-server servertag] hour minute day_of_month month day_of_week command 230sub cmd_jobadd { 231 my ($data, $server, $channel) = @_; 232 233 $server = $server->{tag}; 234 my $disabled = 0; 235 my $repeats = -1; 236 while ($data =~ /^\s*-/) { 237 if ($data =~ s/^\s*-disabled\s+//) { 238 $disabled = 1; 239 next; 240 } 241 if ($data =~ s/^\s*-(\d+)\s+//) { 242 $repeats = $1; 243 next; 244 } 245 my $comm; 246 ($comm, $server, $data) = split(' ', $data, 3); 247 if ($comm ne '-server') { 248 Irssi::print("Bad switch: '$comm'"); 249 return; 250 } 251 } 252 my ($hour, $minute, $dom, $month, $dow, $commands) = split(' ', $data, 6); 253 254 push (@jobs, { 'hour' => $hour, 'minute' => $minute, 'dom' => $dom, 255 'month' => $month, 'dow' => $dow, 256 'server' => $server, 'commands' => $commands, 257 'disabled' => $disabled, 'repeats' => $repeats } ); 258 Irssi::print("Job added"); 259} 260 261sub cmd_jobssave { 262 if (not open (FILE, ">", $savefile)) { 263 Irssi::print("Could not open file '$savefile': $!"); 264 return; 265 } 266 foreach (0 .. $#jobs) { 267 next if ($jobs[$_]->{'repeats'} == 0); # don't save finished jobs 268 print FILE 269 ($jobs[$_]->{'repeats'}>0 ? "-$jobs[$_]->{'repeats'} " : "") 270 . ($jobs[$_]{'disabled'}?"-disabled ":"") 271 ."-server $jobs[$_]{server} " 272 ."$jobs[$_]{hour} $jobs[$_]{minute} $jobs[$_]{dom} " 273 ."$jobs[$_]{month} $jobs[$_]{dow} " 274 ."$jobs[$_]{commands}\n"; 275 } 276 close FILE; 277 Irssi::print("Jobs saved"); 278} 279 280sub cmd_jobsload { 281 if (not open (FILE, q{<}, $savefile)) { 282 Irssi::print("Could not open file '$savefile': $!"); 283 return; 284 } 285 @jobs = (); 286 287 while (<FILE>) { 288 chomp; 289 cmd_jobadd($_, undef, undef); 290 } 291 292 close FILE; 293 Irssi::print("Jobs loaded"); 294} 295 296cmd_jobsload(); 297 298Irssi::command_bind('jobs', 'cmd_jobs', 'Cron'); 299Irssi::command_bind('jobadd', 'cmd_jobadd', 'Cron'); 300Irssi::command_bind('jobdel', 'cmd_jobdel', 'Cron'); 301Irssi::command_bind('jobdisable', 'cmd_jobdisable', 'Cron'); 302Irssi::command_bind('jobenable', 'cmd_jobenable', 'Cron'); 303Irssi::command_bind('jobssave', 'cmd_jobssave', 'Cron'); 304Irssi::command_bind('jobsload', 'cmd_jobsload', 'Cron'); 305 306# vim:noexpandtab:ts=4 307