38ce70d978bd7af59bea4ff4483395d6f5cf9577
[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");
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     do_schedule();
111     do_decision();
112     save();
113     do_custom();
114     if($last==1) {
115         $last=0;
116     } else {
117         $last=int(time/60)*60;
118     }
119 }
120
121 sub ping {
122     open(SOUT, ">&STDOUT");
123     close(STDOUT);
124     my $ret=!(system('ping','-q','-c','1',shift)>>8);
125     open(STDOUT, ">&SOUT");
126     close(SOUT);
127     return $ret;
128 }
129
130 sub do_schedule {
131     my @now=localtime;
132     my $now=$now[2]*60+$now[1];
133     my $familiar = 0;
134
135     open(SCHEDULE, "today --category=school|");
136     while(<SCHEDULE>) {
137         my ($begin, $end, $maxend);
138         next unless /(\d\d):(\d\d)-(\d\d):(\d\d) (.*)/;
139         my ($hs,$ms,$he,$me,$ev) = ($1, $2, $3, $4, $5);
140         $ev =~ s/ *\[.*$//;
141         $begin = $hs*60+$ms-25;
142         $maxend = $he*60+$me+10;
143         $end = ($begin+25)*3/4+($maxend-10)/4;
144         if($begin <= $now && $now < $end) {
145             $state{'class'} = $ev;
146             $familiar = 1;
147             last;
148         } elsif (($state{'class'}||'') eq $ev) {
149             $familiar = 1;
150             undef $state{'class'}
151                 if ($now > $maxend || $state{'phone'} eq "present");
152         }
153     }
154     close(SCHEDULE);
155     undef $state{'class'} unless ($familiar);
156
157
158     open(SCHEDULE, "today --category='!school !private'|");
159     $familiar = 0;
160     while(<SCHEDULE>) {
161         my ($begin, $end, $maxend);
162         next unless /(\d\d):(\d\d)-(\d\d):(\d\d) (.*)/;
163         my ($hs,$ms,$he,$me,$ev) = ($1, $2, $3, $4, $5);
164         $ev =~ s/ *\[.*$//;
165         $begin = $hs*60+$ms;
166         $maxend = $he*60+$me;
167         $end = ($begin)*3/4+($maxend)/4;
168         if($begin <= $now && $now < $end) {
169             $state{'schedule'} = $ev;
170             $familiar = 1;
171             last;
172         } elsif (($state{'schedule'}||'') eq $ev) {
173             $familiar = 1;
174             undef $state{'schedule'}
175                 if ($now > $maxend || $state{'phone'} eq "present");
176         }
177     }
178     close(SCHEDULE);
179     undef $state{'schedule'} unless ($familiar);
180 }
181
182 sub do_chat {
183     if(-r ($ENV{'HOME'} . "/.chat")) {
184         open TMP, $ENV{'HOME'} . "/.chat";
185         my $chat = join ("", <TMP>);
186         close TMP;
187         chomp $chat;
188         $state{'chat'} = $chat;
189     } else {
190         undef $state{'chat'};
191     }
192 }
193
194 sub do_hosts {
195     my (@check) = ("mona", "lisa", "homer", "sarah");
196     my (@uphosts, @livehosts, $host, $hostlist);
197     if(($_[0] || 0) == 1) {
198         $hostlist=$state{'hosts'};
199         $hostlist=~s/0\S* ?//g;
200         @livehosts = split / /, $hostlist;
201     } else {
202         $hostlist="";
203         foreach $host (@check) {
204             push @uphosts, $host if(ping($host));
205         }
206         foreach $host (@uphosts) {
207             if(is_alive($host)) {
208                 push @livehosts,$host;
209                 $hostlist="$host $hostlist";
210             } else {
211                 $hostlist="0$host $hostlist";
212             }
213         }
214         $hostlist=~s/ $//;
215         $state{'hosts'}=$hostlist;
216     }
217     if(scalar @livehosts == 0) {
218         $state{'alive'} = '';
219     } elsif(scalar @livehosts > 1 && $state{'chat'}) {
220         foreach $host (@livehosts) {
221             if ($host eq $state{'chat'}) {
222                 $state{'alive'} = $host;
223                 return;
224             }
225         }
226     }
227     $state{'alive'}=$livehosts[0];
228 }
229
230 sub is_alive {
231     my $ret;
232     eval {
233         local $SIG{ALRM} = sub { die "alarm\n" };
234         alarm(30);
235         $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 marge .*(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);
236         alarm(0);
237     };
238     if($@) {
239         undef $ret;
240         die unless $@ eq "alarm\n";
241     }
242     return $ret;
243 }
244
245 sub do_phone {
246     my $phone;
247     if(!ping('mona')) {
248         $phone="unknown";
249     } else {
250         my $last_slh=`@ssh mona cat .blue/last_slh 2>/dev/null`;
251         if(!$last_slh) {
252             $phone="unknown";
253         } elsif (time-$last_slh < 540) {
254             $phone="present";
255         } else {
256             $phone="absent";
257         }
258     }
259     if($state{'phone'} ne 'present' && $phone eq 'present') {
260         custom_out("");
261     }
262     $state{'phone'} = $phone;
263 }
264
265 sub do_power {
266     open TMP, "upsc milhouse\@localhost|";
267     my $ups='';
268     while(<TMP>) {
269         chomp;
270         if(/status: (.*)/i) {
271             $ups=$1;
272         }
273     }
274     close TMP;
275     $state{'ups'}=$ups;
276 }
277
278 sub do_decision {
279     custom_away("")
280         if ($state{'customawayexp'} && $state{'customawayexp'}<time);
281     custom_out("")
282         if ($state{'customoutexp'} && $state{'customoutexp'}<time);
283     custom_activity("")
284         if ($state{'customactivityexp'} && $state{'customactivityexp'}<time);
285     if ($state{'customactivity'}) {
286         $state{'activity'}=$state{'customactivity'};
287     } elsif ($state{'ups'} eq 'OB' || $state{'ups'} eq 'LB') {
288         $state{'activity'}="Power outage";
289     } else {
290         undef $state{'activity'};
291     }
292     if ($state{'customout'} && $state{'phone'} eq 'absent') {
293         $state{'away'}=$state{'customout'};
294     } elsif ($state{'customaway'}) {
295         $state{'away'}=$state{'customaway'};
296     } elsif ($state{'schedule'}) {
297         $state{'away'}=$state{'schedule'};
298     } elsif ($state{'phone'} ne 'present' && $state{'class'}) {
299         $state{'away'}="Class: ".$state{'class'};
300     } elsif ($state{'alive'}) {
301         undef($state{'away'});
302     } elsif ($state{'phone'} eq 'absent') {
303         $state{'away'}="Out";
304     } elsif ($state{'phone'} ne 'absent') {
305         my @now=localtime;
306         if (1 <= $now[2] && $now[2] < 9) {
307             $state{'away'}="Sleeping";
308         } else {
309             $state{'away'}="Away from keyboard";
310         }
311     }
312 }
313
314 sub host_check {
315     return undef unless($_[0]);
316     my $host=$_[0];
317     if($host eq "phone") {
318         do_phone();
319         do_decision();
320         save();
321         do_custom();
322     } elsif($host eq "chat") {
323         do_chat();
324         do_hosts(1);
325         do_decision();
326         save();
327     } elsif($state{'hosts'} =~ /0$host/ && is_alive($_[0])) {
328         $state{'hosts'} =~ s/0$host/$host/;
329         do_hosts(1);
330         do_decision();
331         save();
332         do_custom();
333     } elsif($state{'hosts'} =~ /(?!0)$host/ && ! is_alive($_[0])) {
334         $state{'hosts'} =~ s/1?$host/0$host/;
335         do_hosts(1);
336         do_decision();
337         save();
338     }
339 }
340
341 sub custom_away {
342     if($_[0]) {
343         $state{'customaway'} = $_[0];
344         $state{'customawayexp'} = time+48*60*60;
345     } else {
346         undef $state{'customaway'};
347         undef $state{'customawayexp'};
348     }
349     do_decision();
350     save();
351 }
352
353 sub custom_out {
354     if($_[0]) {
355         $state{'customout'} = $_[0];
356         $state{'customoutexp'} = time+14*24*60*60;
357     } else {
358         undef $state{'customout'};
359         undef $state{'customoutexp'};
360     }
361     do_decision();
362     save();
363 }
364
365 sub custom_activity {
366     if($_[0]) {
367         $state{'customactivity'} = $_[0];
368         $state{'customactivityexp'} = time+12*60*60;
369     } else {
370         undef $state{'customactivity'};
371         undef $state{'customactivityexp'};
372     }
373     do_decision();
374     save();
375 }
376
377 sub save {
378     if (defined($state{'activity'})) {
379         $state{'status'}=$state{'activity'};
380     } elsif (defined($state{'away'})) {
381         $state{'status'}=$state{'away'};
382     } elsif ($state{'alive'} eq "mona" || $state{'alive'} eq "homer") {
383         $state{'status'}="On desktop";
384     } elsif ($state{'alive'} eq "lisa") {
385         $state{'status'}="On laptop";
386     } elsif ($state{'alive'} eq "sarah") {
387         $state{'status'}="In bed";
388     }
389     open CONF, '>' . $ENV{'HOME'} . "/.away-tpope" || die $!;
390     foreach my $k (keys %state) {
391         my $val=$state{$k};
392         next unless($val);
393         $val =~ s/\n/\\n/g;
394         $val =~ s/"/\\"/g;
395         print CONF "$k=\"$val\"\n";
396     }
397     close CONF;
398     if(defined($state{'activity'})) {
399         open TMP, '>' . $ENV{'HOME'} . "/.activity";
400         print TMP $state{'activity'}."\n";
401         close TMP;
402     } else {
403         unlink($ENV{'HOME'} . "/.activity");
404     }
405     if(defined($state{'away'})) {
406         open TMP, '>' . $ENV{'HOME'} . "/.away";
407         print TMP $state{'away'}."\n";
408         close TMP;
409     } else {
410         unlink($ENV{'HOME'} . "/.away");
411     }
412     open TMP, '>' . $ENV{'HOME'} . "/.status";
413     print TMP $state{'status'}."\n";
414     close TMP;
415 }
416
417 sub load {
418     open CONF, '<' . $ENV{'HOME'} . "/.away-tpope" or return;
419     while(my $line=<CONF>) {
420         $line =~ s/\\n/\n/g;
421         $line =~ s/\\"/"/g;
422         $line =~ s/"$//;
423         $line =~ s/^([^=]*)="?//;
424         chomp $line;
425         $state{$1}=$line;
426     }
427     close CONF;
428 }
429
430 sub hup_handler {
431     $last=1;
432 }
433
434 sub restart_handler {
435     alarm 0;
436     unlink $pipe;
437     unlink "/tmp/.away-tpope.pid";
438     save();
439     exec($0,$arg)  || die $!;
440     print "Restarting $0 $arg...\n";
441 }
442
443 sub quit_handler {
444     unlink $pipe;
445     unlink "/tmp/.away-tpope.pid";
446     save();
447     exit 0;
448 }
449
450 sub do_custom {
451     system("away-actions &");
452 }