New hostnames
[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'}||'') eq "jmwaller") {
199         internal_out("Work",3*60*60);
200     } elsif(($state{'chat'}||'') =~ /^tpope-\d+$/) {
201         internal_out("Work",3*60*60);
202     } elsif(($state{'chat'}||'') eq "accd") {
203         #internal_out("School",30*60);
204     }
205 }
206
207 sub do_hosts {
208     my (@check) = ("tobias", "lucille", "lindsay", "buster");
209     my (@uphosts, @livehosts, $host, $hostlist);
210     if(($_[0] || 0) == 1) {
211         $hostlist=$state{'hosts'};
212         $hostlist=~s/0\S* ?//g;
213         @livehosts = split / /, $hostlist;
214     } else {
215         $hostlist="";
216         foreach $host (@check) {
217             push @uphosts, $host if(ping($host));
218         }
219         foreach $host (@uphosts) {
220             if(is_alive($host)) {
221                 push @livehosts,$host;
222                 $hostlist="$host $hostlist";
223             } else {
224                 $hostlist="0$host $hostlist";
225             }
226         }
227         $hostlist=~s/ $//;
228         $state{'hosts'}=$hostlist;
229     }
230     if(scalar @livehosts == 0) {
231         $state{'alive'} = '';
232     } elsif(scalar @livehosts > 1 && $state{'chat'}) {
233         foreach $host (@livehosts) {
234             if ($host eq $state{'chat'}) {
235                 $state{'alive'} = $host;
236                 return;
237             }
238         }
239     }
240     $state{'alive'}=$livehosts[0];
241 }
242
243 sub is_alive {
244     my $ret;
245     eval {
246         local $SIG{ALRM} = sub { die "alarm\n" };
247         alarm(30);
248         $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 michael .*(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);
249         alarm(0);
250     };
251     if($@) {
252         undef $ret;
253         die unless $@ eq "alarm\n";
254     }
255     return $ret;
256 }
257
258 sub do_phone {
259     my $phone;
260     if(!ping('tobias')) {
261         $phone="unknown";
262     } else {
263         my $last_slh=`@ssh tobias cat .blue/last_slh 2>/dev/null`;
264         if(!$last_slh) {
265             $phone="unknown";
266         } elsif (time-$last_slh < 540) {
267             $phone="present";
268         } else {
269             $phone="absent";
270         }
271     }
272     if(($state{'phone'}||"") ne 'present' && $phone eq 'present') {
273         custom_out("");
274         internal_out("");
275     }
276     $state{'phone'} = $phone;
277 }
278
279 sub do_power {
280     open TMP, "upsc milhouse\@localhost 2>/dev/null|";
281     my $ups='';
282     while(<TMP>) {
283         chomp;
284         if(/status: (.*)/i) {
285             $ups=$1;
286         }
287     }
288     close TMP;
289     $state{'ups'}=$ups;
290 }
291
292 sub do_decision {
293     custom_away("")
294         if ($state{'customawayexp'} && $state{'customawayexp'}<time);
295     custom_out("")
296         if ($state{'customoutexp'} && $state{'customoutexp'}<time);
297     internal_out("")
298         if ($state{'internaloutexp'} && $state{'internaloutexp'}<time);
299     custom_activity("")
300         if ($state{'customactivityexp'} && $state{'customactivityexp'}<time);
301     if (exists($state{'customactivity'})) {
302         $state{'activity'}=$state{'customactivity'};
303     } elsif ($state{'ups'} eq 'OB' || $state{'ups'} eq 'LB') {
304         $state{'activity'}="Power outage";
305     } else {
306         undef $state{'activity'};
307     }
308     if ($state{'customout'} && $state{'phone'} eq 'absent') {
309         $state{'away'}=$state{'customout'};
310     } elsif ($state{'customaway'}) {
311         $state{'away'}=$state{'customaway'};
312     } elsif ($state{'schedule'}) {
313         $state{'away'}=$state{'schedule'};
314     } elsif ($state{'phone'} ne 'present' && $state{'class'}) {
315         $state{'away'}="Class: ".$state{'class'};
316     } elsif ($state{'internalout'} && $state{'phone'} eq 'absent') {
317         $state{'away'}=$state{'internalout'};
318     } elsif ($state{'alive'}) {
319         undef($state{'away'});
320     } elsif ($state{'phone'} eq 'absent') {
321         $state{'away'}="Out";
322     } elsif ($state{'phone'} ne 'absent') {
323         my @now=localtime;
324         if (0 <= $now[2] && $now[2] < 7) {
325             $state{'away'}="Sleeping";
326         } else {
327             $state{'away'}="Away from keyboard";
328         }
329     }
330 }
331
332 sub host_check {
333     return undef unless($_[0]);
334     my $host=$_[0];
335     if($host eq "phone") {
336         do_phone();
337         do_decision();
338         save();
339         do_custom();
340     } elsif($host eq "chat") {
341         do_chat();
342         do_hosts(1);
343         do_decision();
344         save();
345     } elsif($state{'hosts'} =~ /0$host/ && is_alive($_[0])) {
346         $state{'hosts'} =~ s/0$host/$host/;
347         do_hosts(1);
348         do_decision();
349         save();
350         do_custom();
351     } elsif($state{'hosts'} =~ /(?!0)$host/ && ! is_alive($_[0])) {
352         $state{'hosts'} =~ s/1?$host/0$host/;
353         do_hosts(1);
354         do_decision();
355         save();
356     }
357 }
358
359 sub custom_away {
360     if($_[0]) {
361         $state{'customaway'} = $_[0];
362         $state{'customawayexp'} = time+48*60*60;
363     } else {
364         undef $state{'customaway'};
365         undef $state{'customawayexp'};
366     }
367     do_decision();
368     save();
369 }
370
371 sub custom_out {
372     if($_[0]) {
373         $state{'customout'} = $_[0];
374         $state{'customoutexp'} = time+14*24*60*60;
375     } else {
376         undef $state{'customout'};
377         undef $state{'customoutexp'};
378     }
379     do_decision();
380     save();
381 }
382
383 sub internal_out {
384     if($_[0]) {
385         $state{'internalout'} = $_[0];
386         $state{'internaloutexp'} = time+($_[1]||150*60);
387     } else {
388         undef $state{'internalout'};
389         undef $state{'internaloutexp'};
390     }
391     do_decision();
392     save();
393 }
394
395 sub custom_activity {
396     if($_[0]) {
397         $state{'customactivity'} = $_[0];
398         $state{'customactivityexp'} = time+12*60*60;
399     } else {
400         undef $state{'customactivity'};
401         undef $state{'customactivityexp'};
402     }
403     do_decision();
404     save();
405 }
406
407 sub save {
408     if (defined($state{'activity'})) {
409         $state{'status'}=$state{'activity'};
410     } elsif (defined($state{'away'})) {
411         $state{'status'}=$state{'away'};
412     } elsif ($state{'alive'} eq "tobias" || $state{'alive'} eq "lucille") {
413         $state{'status'}="On desktop";
414     } elsif ($state{'alive'} eq "lindsay") {
415         $state{'status'}="On laptop";
416     } elsif ($state{'alive'} eq "buster") {
417         $state{'status'}="In bed";
418     }
419     open CONF, '>' . $ENV{'HOME'} . "/.away-tpope" || die $!;
420     foreach my $k (keys %state) {
421         my $val=$state{$k};
422         next unless($val);
423         $val =~ s/\n/\\n/g;
424         $val =~ s/"/\\"/g;
425         print CONF "$k=\"$val\"\n";
426     }
427     close CONF;
428     if(defined($state{'activity'})) {
429         open TMP, '>' . $ENV{'HOME'} . "/.activity";
430         print TMP $state{'activity'}."\n";
431         close TMP;
432     } else {
433         unlink($ENV{'HOME'} . "/.activity");
434     }
435     if(defined($state{'away'})) {
436         open TMP, '>' . $ENV{'HOME'} . "/.away";
437         print TMP $state{'away'}."\n";
438         close TMP;
439     } else {
440         unlink($ENV{'HOME'} . "/.away");
441     }
442     open TMP, '>' . $ENV{'HOME'} . "/.status";
443     print TMP $state{'status'}."\n";
444     close TMP;
445 }
446
447 sub load {
448     open CONF, '<' . $ENV{'HOME'} . "/.away-tpope" or return;
449     undef $state{'customactivity'};
450     undef $state{'customaway'};
451     undef $state{'customout'};
452     undef $state{'internalout'};
453     while(my $line=<CONF>) {
454         $line =~ s/\\n/\n/g;
455         $line =~ s/\\"/"/g;
456         $line =~ s/"$//;
457         $line =~ s/^([^=]*)="?//;
458         chomp $line;
459         $state{$1}=$line;
460     }
461     close CONF;
462 }
463
464 sub hup_handler {
465     $last=1;
466 }
467
468 sub restart_handler {
469     alarm 0;
470     unlink $pipe;
471     unlink "/tmp/.away-tpope.pid";
472     save();
473     exec($0,$arg)  || die $!;
474     print "Restarting $0 $arg...\n";
475 }
476
477 sub quit_handler {
478     unlink $pipe;
479     unlink "/tmp/.away-tpope.pid";
480     save();
481     exit 0;
482 }
483
484 sub do_custom {
485     system("away-actions &");
486 }