Committing all changes before migrating servers
[tpope-extra.git] / perl / away-tpope
1 #!/usr/bin/perl -w
2 # $Id$
3 # -*- perl -*- vim:set ft=perl sw=4 sts=4:
4
5 use strict;
6 use vars qw(%state @ssh $last $pipe $arg $slow);
7 @ssh=("ssh","-a","-x","-oBatchmode=yes","-oSetupTimeOut=20","-qq");
8 $pipe = "/tmp/.away-tpope";
9
10 $arg=shift || "";
11 daemonize() if ($arg eq "-d");
12 daemon() if ($arg eq "-D");
13
14
15 if($arg eq "activity" || $arg eq "away" || $arg eq "out" || $arg eq "check") {
16     if(exists($ARGV[0])) {
17         die "Daemon not running\n" unless (-p $pipe);
18         my $a = join(" ",@ARGV);
19         $a =~ s/^-c$//;
20         open FIFO, ">$pipe";
21         print FIFO "$arg $a";
22     } else {
23         load();
24         print $state{$arg}."\n" if($state{$arg});
25     }
26 } else {
27     exit 1;
28 }
29
30 sub daemon {
31     die "Daemon already running?\n" if (-e "/tmp/.away-tpope.pid");
32     open TMP, ">/tmp/.away-tpope.pid";
33     print TMP "$$\n";
34     close TMP;
35     load();
36     $SIG{'INT'} = \&quit_handler;
37     $SIG{'TERM'} = \&quit_handler;
38     $SIG{'QUIT'} = \&quit_handler;
39     $SIG{'HUP'} = \&hup_handler;
40     $SIG{'USR2'} = \&restart_handler;
41     die "Daemon already running\n" if (-r "/tmp/.tpope-away.pid");
42     unless (-p $pipe) {
43         unlink $pipe;
44         system("mkfifo", $pipe);
45         chmod 0600, $pipe;
46     }
47     $last=0;
48     $slow=1;
49     cycle();
50     while(1) {
51         main_loop();
52     }
53 }
54
55 sub daemonize {
56     chdir "/";
57     my $pid=fork();
58     if($pid) {
59         exit(0)
60     }
61     elsif(defined($pid)) {
62         daemon();
63     } else { exit(1); }
64 }
65
66 sub main_loop {
67     my $read="";
68     eval {
69         local $SIG{ALRM} = sub { die "alarm\n" };
70         alarm 1;
71         open FIFO, "<$pipe" and
72         $read = <FIFO>;
73         alarm 0;
74         close FIFO;
75     };
76     if($@) {
77         die unless $@ eq "alarm\n";
78         close FIFO;
79     }
80     if($read) {
81         chomp $read;
82         $read =~ s/^([a-z]*)( |$)//;
83         my $c = $1 || "";
84             if($c eq "activity") {
85                 custom_activity($read);
86             } elsif($c eq "away") {
87                 custom_away($read);
88             } elsif($c eq "out") {
89                 custom_out($read);
90             } elsif($c eq "check") {
91                 host_check($read);
92             } else {
93                 print "Unknown command: $c\n";
94             }
95     }
96     if(time-$last >= 240) {
97         cycle();
98     }
99 }
100
101 sub cycle {
102     local $SIG{'HUP'} = sub {$last = 1};
103     do_chat();
104     if((!$last) || $slow++ ) {
105         do_hosts();
106         do_power();
107         do_phone();
108         $slow=0;
109     }
110     eval_chat();
111     do_schedule();
112     do_decision();
113     save();
114     do_custom();
115     if($last==1) {
116         $last=0;
117     } else {
118         $last=int(time/60)*60;
119     }
120 }
121
122 sub ping {
123     open(SOUT, ">&STDOUT");
124     close(STDOUT);
125     my $ret=!(system('ping','-q','-c','1',shift)>>8);
126     open(STDOUT, ">&SOUT");
127     close(SOUT);
128     return $ret;
129 }
130
131 sub do_schedule {
132     my @now=localtime;
133     my $now=$now[2]*60+$now[1];
134     my $familiar = 0;
135
136     open(SCHEDULE, "today --category=school|");
137     while(<SCHEDULE>) {
138         my ($begin, $end, $maxend);
139         next unless /(\d\d):(\d\d)-(\d\d):(\d\d) (.*)/;
140         my ($hs,$ms,$he,$me,$ev) = ($1, $2, $3, $4, $5);
141         $ev =~ s/ *\[.*$//;
142         $begin = $hs*60+$ms-10;
143         $maxend = $he*60+$me+10;
144         $end = ($begin+10)*3/4+($maxend-10)/4;
145         if($begin <= $now && $now < $end) {
146             $state{'class'} = $ev;
147             internal_out("School");
148             $familiar = 1;
149             last;
150         } elsif($begin-35<=$now && $now<$end && $state{'phone'} eq "absent") {
151             internal_out("School");
152         } elsif (($state{'class'}||'') eq $ev) {
153             $familiar = 1;
154             undef $state{'class'}
155                 if ($now > $maxend || $state{'phone'} eq "present");
156         }
157     }
158     close(SCHEDULE);
159     undef $state{'class'} unless ($familiar);
160
161     open(SCHEDULE, "today --category='!school !private'|");
162     $familiar = 0;
163     while(<SCHEDULE>) {
164         my ($begin, $end, $maxend);
165         next unless /(\d\d):(\d\d)-(\d\d):(\d\d) (.*)/;
166         my ($hs,$ms,$he,$me,$ev) = ($1, $2, $3, $4, $5);
167         $ev =~ s/ *\[.*$//;
168         $begin = $hs*60+$ms;
169         $maxend = $he*60+$me;
170         $end = ($begin)*3/4+($maxend)/4;
171         if($begin <= $now && $now < $end) {
172             $state{'schedule'} = $ev;
173             $familiar = 1;
174             last;
175         } elsif (($state{'schedule'}||'') eq $ev) {
176             $familiar = 1;
177             undef $state{'schedule'}
178                 if ($now > $maxend || $state{'phone'} eq "present");
179         }
180     }
181     close(SCHEDULE);
182     undef $state{'schedule'} unless ($familiar);
183 }
184
185 sub do_chat {
186     if(-r ($ENV{'HOME'} . "/.chat")) {
187         open TMP, $ENV{'HOME'} . "/.chat";
188         my $chat = join ("", <TMP>);
189         close TMP;
190         chomp $chat;
191         $state{'chat'} = $chat;
192     } else {
193         undef $state{'chat'};
194     }
195 }
196
197 sub eval_chat {
198     if(($state{'chat'}||'') =~ /^(tpope-\d+|jmwaller|arwen|george)$/) {
199         internal_out("Work",3*60*60);
200     } elsif(($state{'chat'}||'') eq "accd") {
201         #internal_out("School",30*60);
202     }
203 }
204
205 sub do_hosts {
206     my (@check) = ("tobias", "lucille", "lindsay", "buster");
207     my (@uphosts, @livehosts, $host, $hostlist);
208     if(($_[0] || 0) == 1) {
209         $hostlist=$state{'hosts'};
210         $hostlist=~s/0\S* ?//g;
211         @livehosts = split / /, $hostlist;
212     } else {
213         $hostlist="";
214         foreach $host (@check) {
215             push @uphosts, $host if(ping($host));
216         }
217         foreach $host (@uphosts) {
218             if(is_alive($host)) {
219                 push @livehosts,$host;
220                 $hostlist="$host $hostlist";
221             } else {
222                 $hostlist="0$host $hostlist";
223             }
224         }
225         $hostlist=~s/ $//;
226         $state{'hosts'}=$hostlist;
227     }
228     if(scalar @livehosts == 0) {
229         $state{'alive'} = '';
230     } elsif(scalar @livehosts > 1 && $state{'chat'}) {
231         foreach $host (@livehosts) {
232             if ($host eq $state{'chat'}) {
233                 $state{'alive'} = $host;
234                 return;
235             }
236         }
237     }
238     $state{'alive'}=$livehosts[0];
239 }
240
241 sub is_alive {
242     my $ret;
243     eval {
244         local $SIG{ALRM} = sub { die "alarm\n" };
245         alarm(30);
246         $ret=!(system(@ssh,shift, 'if pidof xscreensaver >/dev/null && DISPLAY=:0.0 xscreensaver-command -version >/dev/null 2>&1; then if DISPLAY=:0.0 xscreensaver-command -time 2>&1 |egrep "non-blanked|no saver status" >/dev/null; then true; else pid=`ps ax|egrep "[0-9]:[0-9][0-9] ssh gob .*(screen.*RR irc|Chat)"|sed -e "s/^  *//"|cut -d" " -f 1`; [ -f "$HOME/.irc.lock" -o -z "$pid" ] || kill $pid; false; fi; else false; fi') >> 8);
247         alarm(0);
248     };
249     if($@) {
250         undef $ret;
251         die unless $@ eq "alarm\n";
252     }
253     return $ret;
254 }
255
256 sub do_phone {
257     my $phone;
258     if(!ping('tobias')) {
259         $phone="unknown";
260     } else {
261         my $last_slh=`@ssh tobias cat .blue/last_slh 2>/dev/null`;
262         if(!$last_slh) {
263             $phone="unknown";
264         } elsif (time-$last_slh < 540) {
265             $phone="present";
266         } else {
267             $phone="absent";
268         }
269     }
270     if(($state{'phone'}||"") ne 'present' && $phone eq 'present') {
271         custom_out("");
272         internal_out("");
273     }
274     $state{'phone'} = $phone;
275 }
276
277 sub do_power {
278     open TMP, "upsc milhouse\@localhost 2>/dev/null|";
279     my $ups='';
280     while(<TMP>) {
281         chomp;
282         if(/status: (.*)/i) {
283             $ups=$1;
284         }
285     }
286     close TMP;
287     $state{'ups'}=$ups;
288 }
289
290 sub do_decision {
291     custom_away("")
292         if ($state{'customawayexp'} && $state{'customawayexp'}<time);
293     custom_out("")
294         if ($state{'customoutexp'} && $state{'customoutexp'}<time);
295     internal_out("")
296         if ($state{'internaloutexp'} && $state{'internaloutexp'}<time);
297     custom_activity("")
298         if ($state{'customactivityexp'} && $state{'customactivityexp'}<time);
299     if (exists($state{'customactivity'})) {
300         $state{'activity'}=$state{'customactivity'};
301     } elsif ($state{'ups'} eq 'OB' || $state{'ups'} eq 'LB') {
302         $state{'activity'}="Power outage";
303     } else {
304         undef $state{'activity'};
305     }
306     if ($state{'customout'} && $state{'phone'} eq 'absent') {
307         $state{'away'}=$state{'customout'};
308     } elsif ($state{'customaway'}) {
309         $state{'away'}=$state{'customaway'};
310     } elsif ($state{'schedule'}) {
311         $state{'away'}=$state{'schedule'};
312     } elsif ($state{'phone'} ne 'present' && $state{'class'}) {
313         $state{'away'}="Class: ".$state{'class'};
314     } elsif ($state{'internalout'} && $state{'phone'} eq 'absent') {
315         $state{'away'}=$state{'internalout'};
316     } elsif ($state{'alive'}) {
317         undef($state{'away'});
318     } elsif ($state{'phone'} eq 'absent') {
319         $state{'away'}="Out";
320     } elsif ($state{'phone'} ne 'absent') {
321         my @now=localtime;
322         if (0 <= $now[2] && $now[2] < 7) {
323             $state{'away'}="Sleeping";
324         } else {
325             $state{'away'}="Away from keyboard";
326         }
327     }
328 }
329
330 sub host_check {
331     return undef unless($_[0]);
332     my $host=$_[0];
333     if($host eq "phone") {
334         do_phone();
335         do_decision();
336         save();
337         do_custom();
338     } elsif($host eq "chat") {
339         do_chat();
340         do_hosts(1);
341         do_decision();
342         save();
343     } elsif($state{'hosts'} =~ /0$host/ && is_alive($_[0])) {
344         $state{'hosts'} =~ s/0$host/$host/;
345         do_hosts(1);
346         do_decision();
347         save();
348         do_custom();
349     } elsif($state{'hosts'} =~ /(?!0)$host/ && ! is_alive($_[0])) {
350         $state{'hosts'} =~ s/1?$host/0$host/;
351         do_hosts(1);
352         do_decision();
353         save();
354     }
355 }
356
357 sub custom_away {
358     if($_[0]) {
359         $state{'customaway'} = $_[0];
360         $state{'customawayexp'} = time+48*60*60;
361     } else {
362         undef $state{'customaway'};
363         undef $state{'customawayexp'};
364     }
365     do_decision();
366     save();
367 }
368
369 sub custom_out {
370     if($_[0]) {
371         $state{'customout'} = $_[0];
372         $state{'customoutexp'} = time+14*24*60*60;
373     } else {
374         undef $state{'customout'};
375         undef $state{'customoutexp'};
376     }
377     do_decision();
378     save();
379 }
380
381 sub internal_out {
382     if($_[0]) {
383         $state{'internalout'} = $_[0];
384         $state{'internaloutexp'} = time+($_[1]||150*60);
385     } else {
386         undef $state{'internalout'};
387         undef $state{'internaloutexp'};
388     }
389     do_decision();
390     save();
391 }
392
393 sub custom_activity {
394     if($_[0]) {
395         $state{'customactivity'} = $_[0];
396         $state{'customactivityexp'} = time+12*60*60;
397     } else {
398         undef $state{'customactivity'};
399         undef $state{'customactivityexp'};
400     }
401     do_decision();
402     save();
403 }
404
405 sub save {
406     if (defined($state{'activity'})) {
407         $state{'status'}=$state{'activity'};
408     } elsif (defined($state{'away'})) {
409         $state{'status'}=$state{'away'};
410     } elsif ($state{'alive'} eq "tobias" || $state{'alive'} eq "lucille") {
411         $state{'status'}="On desktop";
412     } elsif ($state{'alive'} eq "lindsay") {
413         $state{'status'}="On laptop";
414     } elsif ($state{'alive'} eq "buster") {
415         $state{'status'}="In bed";
416     }
417     open CONF, '>' . $ENV{'HOME'} . "/.away-tpope" || die $!;
418     foreach my $k (keys %state) {
419         my $val=$state{$k};
420         next unless($val);
421         $val =~ s/\n/\\n/g;
422         $val =~ s/"/\\"/g;
423         print CONF "$k=\"$val\"\n";
424     }
425     close CONF;
426     if(defined($state{'activity'})) {
427         open TMP, '>' . $ENV{'HOME'} . "/.activity";
428         print TMP $state{'activity'}."\n";
429         close TMP;
430     } else {
431         unlink($ENV{'HOME'} . "/.activity");
432     }
433     if(defined($state{'away'})) {
434         open TMP, '>' . $ENV{'HOME'} . "/.away";
435         print TMP $state{'away'}."\n";
436         close TMP;
437     } else {
438         unlink($ENV{'HOME'} . "/.away");
439     }
440     open TMP, '>' . $ENV{'HOME'} . "/.status";
441     print TMP $state{'status'}."\n";
442     close TMP;
443 }
444
445 sub load {
446     open CONF, '<' . $ENV{'HOME'} . "/.away-tpope" or return;
447     undef $state{'customactivity'};
448     undef $state{'customaway'};
449     undef $state{'customout'};
450     undef $state{'internalout'};
451     while(my $line=<CONF>) {
452         $line =~ s/\\n/\n/g;
453         $line =~ s/\\"/"/g;
454         $line =~ s/"$//;
455         $line =~ s/^([^=]*)="?//;
456         chomp $line;
457         $state{$1}=$line;
458     }
459     close CONF;
460 }
461
462 sub hup_handler {
463     $last=1;
464 }
465
466 sub restart_handler {
467     alarm 0;
468     unlink $pipe;
469     unlink "/tmp/.away-tpope.pid";
470     save();
471     exec($0,$arg)  || die $!;
472     print "Restarting $0 $arg...\n";
473 }
474
475 sub quit_handler {
476     unlink $pipe;
477     unlink "/tmp/.away-tpope.pid";
478     save();
479     exit 0;
480 }
481
482 sub do_custom {
483     system("away-actions &");
484 }