Permalink
Browse files

added --idle and --output modes to monitor TTY and issue notifies

  • Loading branch information...
1 parent 48eaa69 commit 2198cadb392271b4d09df52a42975fa5e2f66889 @grantm committed Sep 21, 2012
Showing with 236 additions and 1 deletion.
  1. +4 −0 plugins/NotifyClient/Changes
  2. +232 −1 plugins/NotifyClient/lib/App/BCVI/NotifyClient.pm
@@ -1,5 +1,9 @@
Revision history for App-BCVI-NotifyClient
+2.00 2012-09-21
+ Added --idle and --output modes to monitor TTY and issue notifications
+ of activity (or lack thereof)
+
1.00 2010-06-03
First version, uploaded to CPAN
@@ -5,13 +5,181 @@ use strict;
our $VERSION = '1.00';
+use POSIX qw(setsid);
+
+my $default_idle_time = 20; # seconds
+my $default_poll_interval = 5; # seconds
+
+sub send_command {
+ my($self, @args) = @_;
+
+ my $command = $self->opt('command') || $self->default_command();
+
+ if($command eq 'notify') {
+ # Check for TTY monitoring options; handle them and exit
+ $self->_notify_handle_tty_monitoring(@args);
+ # Otherwise drop through for default handling
+ }
+
+ return $self->SUPER::send_command(@args);
+}
+
+
+sub _notify_handle_tty_monitoring {
+ my($self, @args) = @_;
+
+ my %opt = $self->_notify_get_options(@args) or return;
+ $opt{tty} ||= $self->_notify_current_tty;
+
+ if($opt{idle}) {
+ print "Starting background process to monitor $opt{tty} for $opt{idle} second idle period\n";
+ print "Kill monitor with: $0 --kill\n";
+ $opt{output} = $default_poll_interval;
+ $self->_notify_fork_bg_tty_monitor(sub {
+ while(1) {
+ $self->_notify_wait_for_idle(\%opt);
+ $self->SUPER::send_command("terminal is idle");
+ $self->_notify_exit if $opt{once};
+ $self->_notify_wait_for_output(\%opt);
+ }
+ });
+ }
+ elsif($opt{output}) {
+ print "Starting background process to monitor $opt{tty} for output\n";
+ print "Kill monitor with: bnotify --kill\n";
+ $opt{idle} = $default_idle_time;
+ $self->_notify_fork_bg_tty_monitor(sub {
+ $self->_notify_wait_for_idle(\%opt);
+ while(1) {
+ $self->_notify_wait_for_output(\%opt);
+ $self->SUPER::send_command("output received");
+ $self->_notify_exit if $opt{once};
+ $self->_notify_wait_for_idle(\%opt);
+ }
+ });
+ }
+ elsif($opt{kill}) {
+ $self->App::BCVI::Server::kill_current_listener();
+ exit;
+ }
+}
+
+
+sub _notify_wait_for_idle {
+ my($self, $opt) = @_;
+
+ my $sleep_time = $opt->{idle};
+ while(1) {
+ sleep($sleep_time);
+ my $mtime = $self->_notify_tty_mtime($opt);
+ my $idle = time() - $mtime;
+ $sleep_time = $opt->{idle} - $idle;
+ return if $sleep_time <= 0;
+ }
+}
+
+
+sub _notify_wait_for_output {
+ my($self, $opt) = @_;
+
+ my $mtime = $self->_notify_tty_mtime($opt);
+ while(1) {
+ sleep($opt->{output});
+ return if $self->_notify_tty_mtime($opt) != $mtime;
+ }
+}
+
+
+sub _notify_tty_mtime {
+ my($self, $opt) = @_;
+
+ my @stat = stat($opt->{tty}) or die "stat($opt->{tty}): $!";
+ return $stat[9];
+}
+
+
+sub _notify_current_tty {
+ my $tty = readlink("/proc/self/fd/0");
+ if(!$tty) {
+ chomp( $tty = `tty` );
+ }
+ die "Unable to determine TTY\n" unless $tty;
+ return $tty;
+}
+
+
+sub _notify_get_options {
+ my($self, @args) = @_;
+
+ local(@ARGV) = @args;
+ my %opt = ();
+ Getopt::Long::GetOptions(\%opt,
+ '--idle|i:i',
+ '--output|o:i',
+ '--tty|t=s',
+ '--once|1',
+ '--kill|k',
+ ) or exit;
+
+ if(defined($opt{idle})) {
+ $opt{idle} ||= $default_idle_time;
+ }
+
+ if(defined($opt{output})) {
+ $opt{output} ||= $default_poll_interval;
+ }
+
+ if(defined($opt{tty}) and ! -e $opt{tty}) {
+ die "No such file or device: $opt{tty}\n";
+ }
+
+ return %opt;
+}
+
+
+sub _notify_fork_bg_tty_monitor {
+ my($self, $monitor_sub) = @_;
+
+ fork() && exit; # Parent retuns to shell child continues in background
+ setsid();
+
+ # We have this code lying around - might as well abuse it
+ $self->App::BCVI::Server::save_pid();
+
+ $monitor_sub->();
+
+ exit; # Shouldn't be reached
+}
+
+
+sub _notify_exit {
+ my($self) = @_;
+
+ unlink $self->pid_file;
+ exit;
+}
+
+
+sub pid_file {
+ my($self) = @_;
+
+ my $path = $self->_notify_current_tty;
+ $path =~ s{^.*(?=tty|pty|pts)}{}i;
+ $path =~ s{^/}{};
+ $path =~ s{\W+}{-}g;
+
+ return File::Spec->catfile($self->conf_directory(), "notify-$path.pid");
+}
+
App::BCVI->register_aliases(
- 'test -n "${BCVI_CONF}" && alias bnotify="bcvi --no-path-xlate -c notify"',
+ 'test -n "${BCVI_CONF}" && alias bnotify="bcvi --no-path-xlate -c notify --"',
);
App::BCVI->register_installable();
+App::BCVI->hook_client_class();
+
1;
@@ -21,6 +189,20 @@ __END__
App::BCVI::NotifyClient - Send a notification message back to the user's desktop
+=head1 SYNOPSIS
+
+Send a message to the desktop notifications widget on your workstation:
+
+ $ long-running-command; bnotify "long-running-command has finished"
+
+or fork a background monitor to advise you when a subsequent command pauses for
+input:
+
+ $ bnotify -i
+ Starting background process to monitor /dev/pts/0 for 5 second idle period
+ $ sudo apt-get dist-upgrade
+ ...
+
=head1 DESCRIPTION
This module is a plugin for C<bcvi> (see: L<App::BCVI>). It uses the C<notify>
@@ -39,13 +221,62 @@ completed on a remote server, for example:
pg_dump intranet >intranet.dump ; bnotify "Database dump is finished!"
+=head1 OPTIONS
+
+Instead of simply sending a message, you can provide options to fork a
+background process which will send you a message later when something
+interesting happens (or doesn't):
+
+=over 4
+
+=item B<< --idle [<seconds>] >> (alias -i)
+
+If the specified number of seconds (default 20) elapse with no output being
+written to the TTY, you will receive a notification that the TTY is idle. For
+example you might use this option to fork a listener and then kick off a
+dist-upgrade command. If the command pauses awaiting input then you will be
+notified.
+
+=item B<< --output [<seconds>] >> (alias -o)
+
+The C<--output> option is essentially the opposite of the C<--idle> - it will
+tell you when there I<has> been output on the TTY. By default it will check
+every 5 seconds. You can specify a different poll interval but this may mean
+your notifications take longer to arrive.
+
+When you specify C<--output>, the listener process will actually wait for 20
+seconds B<of idle time> before it starts looking for output. This allows you
+to kick off the command you wish to monitor without getting alerts as you
+type.
+
+=item B<< --tty <path> >> (alias -t)
+
+The C<--idle> and C<--output> options will monitor the current TTY by default.
+This option allows you to monitor a different TTY or even a plain file.
+
+=item B<< --once >> (alias -1)
+
+The C<--idle> and C<--output> option will loop and notify you of each idle time
+or output event. Use the C<<--once>> option if you only want to be told about
+the first event. The background process will exit immediately after sending
+the notification.
+
+=item B<< --kill >> (alias -k)
+
+Find and kill the background listener associated with the current TTY.
+
+=back
=head1 SUPPORT
You can look for information at:
=over 4
+=item * Source code
+
+L<https://github.com/grantm/bcvi> (under 'plugins')
+
=item * RT: CPAN's request tracker
L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=App-BCVI-NotifyClient>

0 comments on commit 2198cad

Please sign in to comment.