summaryrefslogtreecommitdiffstats
path: root/scripts/cron.pl
blob: 0d1ac1eb20104e9cca00a2d81d1723b35100b783 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
#
# Short help/usage: 
# /jobadd hour minute day_of_month month day_of_week command
# Possibile switches for jobadd: 
#	-disabled
#	-server <tag>
#	-<number>
# /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 -<number> to execute job only <number> 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 (<FILE>) {
		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