about summary refs log tree commit homepage
path: root/lib
diff options
context:
space:
mode:
authorEric Wong <e@80x24.org>2021-02-07 23:05:12 -1000
committerEric Wong <e@80x24.org>2021-02-08 22:07:43 +0000
commitfb6aa4da304125a3fb7932e1335be85ff260e031 (patch)
tree63e230503bdfc4c11a0888e80f5eae48194dc3f0 /lib
parent80ba871c557eb5ba79c73150519ce9ea5225d695 (diff)
downloadpublic-inbox-fb6aa4da304125a3fb7932e1335be85ff260e031.tar.gz
For --mua users writing to lock-free -o MFOLDER destinations;
we'll keep -WINCH and send an ASCII terminal bell when results
are complete.  This is intended to let early MUA spawners know
when lei2mail is done writing results.

We'll also support running arbitrary commands.  It may be used
to run play(1) (from SoX), handle pipelines+redirects
(e.g. "/bin/sh -c 'echo search done | wall'") or other commands.
Diffstat (limited to 'lib')
-rw-r--r--lib/PublicInbox/LEI.pm54
-rw-r--r--lib/PublicInbox/LeiOverview.pm5
2 files changed, 43 insertions, 16 deletions
diff --git a/lib/PublicInbox/LEI.pm b/lib/PublicInbox/LEI.pm
index e95a674b..7b2a3e6f 100644
--- a/lib/PublicInbox/LEI.pm
+++ b/lib/PublicInbox/LEI.pm
@@ -112,7 +112,7 @@ our %CMD = ( # sorted in order of importance/use:
         save-as=s output|mfolder|o=s format|f=s dedupe|d=s threads|t augment|a
         sort|s=s reverse|r offset=i remote! local! external! pretty
         include|I=s@ exclude=s@ only=s@ jobs|j=s globoff|g stdin|
-        mua=s no-torsocks torsocks=s verbose|v+ quiet|q),
+        alert=s@ mua=s no-torsocks torsocks=s verbose|v+ quiet|q),
         PublicInbox::LeiQuery::curl_opt(), opt_dash('limit|n=i', '[0-9]+') ],
 
 'show' => [ 'MID|OID', 'show a given object (Message-ID or object ID)',
@@ -227,6 +227,11 @@ my %OPTDESC = (
 'show        threads|t' => 'display entire thread a message belongs to',
 'q        threads|t' =>
         'return all messages in the same threads as the actual match(es)',
+'alert=s@' => ['CMD,-WINCH,-bell,<any command>',
+        'run command(s) or perform ops when done writing to output ' .
+        '(default: "-WINCH,-bell" with --mua and Maildir/IMAP output, ' .
+        'nothing otherwise)' ],
+
 'augment|a' => 'augment --output destination instead of clobbering',
 
 'output|mfolder|o=s' => [ 'MFOLDER',
@@ -739,21 +744,43 @@ sub start_mua {
         if (my $sock = $self->{sock}) { # lei(1) client process runs it
                 send($sock, exec_buf(\@cmd, {}), MSG_EOR);
         } elsif ($self->{oneshot}) {
-                $self->{"mua.pid.$self.$$"} = spawn(\@cmd);
+                $self->{"pid.$self.$$"}->{spawn(\@cmd)} = \@cmd;
         }
         if ($self->{lxs} && $self->{au_done}) { # kick wait_startq
                 syswrite($self->{au_done}, 'q' x ($self->{lxs}->{jobs} // 0));
         }
+        $self->{opt}->{quiet} = 1;
+        delete $self->{-progress};
+        delete $self->{opt}->{verbose};
 }
 
 sub poke_mua { # forces terminal MUAs to wake up and hopefully notice new mail
         my ($self) = @_;
-        return unless $self->{opt}->{mua} && -t $self->{1};
-        # hit the process group that started the MUA
-        if (my $s = $self->{sock}) {
-                send($s, '-WINCH', MSG_EOR);
-        } elsif ($self->{oneshot}) {
-                kill('-WINCH', $$);
+        my $alerts = $self->{opt}->{alert} // return;
+        while (my $op = shift(@$alerts)) {
+                if ($op eq '-WINCH') {
+                        # hit the process group that started the MUA
+                        if ($self->{sock}) {
+                                send($self->{sock}, '-WINCH', MSG_EOR);
+                        } elsif ($self->{oneshot}) {
+                                kill('-WINCH', $$);
+                        }
+                } elsif ($op eq '-bell') {
+                        out($self, "\a");
+                } elsif ($op =~ /(?<!\\),/) { # bare ',' (not ',,')
+                        push @$alerts, split(/(?<!\\),/, $op);
+                } elsif ($op =~ m!\A([/a-z0-9A-Z].+)!) {
+                        my $cmd = $1; # run an arbitrary command
+                        require Text::ParseWords;
+                        $cmd = [ Text::ParseWords::shellwords($cmd) ];
+                        if (my $s = $self->{sock}) {
+                                send($s, exec_buf($cmd, {}), MSG_EOR);
+                        } elsif ($self->{oneshot}) {
+                                $self->{"pid.$self.$$"}->{spawn($cmd)} = $cmd;
+                        }
+                } else {
+                        err($self, "W: unsupported --alert=$op"); # non-fatal
+                }
         }
 }
 
@@ -776,8 +803,8 @@ sub start_pager {
                 my $fds = [ map { fileno($_) } @$rdr{0..2} ];
                 $send_cmd->($sock, $fds, exec_buf([$pager], $new_env), MSG_EOR);
         } elsif ($self->{oneshot}) {
-                $pgr->[0] = spawn([$pager], $new_env, $rdr);
-                $pgr->[3] = $$; # ew'll reap it
+                my $cmd = [$pager];
+                $self->{"pid.$self.$$"}->{spawn($cmd, $new_env, $rdr)} = $cmd;
         } else {
                 die 'BUG: start_pager w/o socket';
         }
@@ -793,8 +820,6 @@ sub stop_pager {
         $self->{2} = $pgr->[2];
         # do not restore original stdout, just close it so we error out
         close(delete($self->{1})) if $self->{1};
-        my $pid = $pgr->[0];
-        dwaitpid($pid) if $pid && ($pgr->[3] // 0) == $$;
 }
 
 sub accept_dispatch { # Listener {post_accept} callback
@@ -1044,9 +1069,8 @@ sub DESTROY {
         my ($self) = @_;
         $self->{1}->autoflush(1) if $self->{1};
         stop_pager($self);
-        if (my $mua_pid = delete $self->{"mua.pid.$self.$$"}) {
-                waitpid($mua_pid, 0);
-        }
+        my $oneshot_pids = delete $self->{"pid.$self.$$"} or return;
+        waitpid($_, 0) for keys %$oneshot_pids;
 }
 
 1;
diff --git a/lib/PublicInbox/LeiOverview.pm b/lib/PublicInbox/LeiOverview.pm
index f0ac4684..98c89d12 100644
--- a/lib/PublicInbox/LeiOverview.pm
+++ b/lib/PublicInbox/LeiOverview.pm
@@ -98,7 +98,10 @@ sub new {
                 $opt->{'sort'} //= 'docid' if $dst ne '/dev/stdout';
                 $lei->{l2m} = eval { PublicInbox::LeiToMail->new($lei) };
                 return $lei->fail($@) if $@;
-                $lei->{early_mua} = 1 if $opt->{mua} && $lei->{l2m}->lock_free;
+                if ($opt->{mua} && $lei->{l2m}->lock_free) {
+                        $lei->{early_mua} = 1;
+                        $opt->{alert} //= [ '-WINCH,-bell' ] if -t $lei->{1};
+                }
         }
         $self;
 }