X-Git-Url: http://www.git.stargrave.org/?a=blobdiff_plain;f=lib%2FPublicInbox%2FLEI.pm;h=8fa8994488e12ae82ca22503bbfad095ae4ca0eb;hb=4ea40d3f60bdf8e2a8b04f99d8e308309f2748cb;hp=a94941a995e8ad88b443de1e4ae1b5c607eee988;hpb=7d3a5e7e0edf0c3aef219d0e03af905f956cf671;p=public-inbox.git diff --git a/lib/PublicInbox/LEI.pm b/lib/PublicInbox/LEI.pm index a94941a9..8fa89944 100644 --- a/lib/PublicInbox/LEI.pm +++ b/lib/PublicInbox/LEI.pm @@ -68,28 +68,31 @@ sub rel2abs ($$) { my ($self, $p) = @_; return $p if index($p, '/') == 0; # already absolute my $pwd = $self->{env}->{PWD}; + my $cwd; if (defined $pwd) { - my $cwd = $self->{3} // getcwd() // die "getcwd(PWD=$pwd): $!"; + my $xcwd = $self->{3} // + ($cwd = getcwd() // die "getcwd(PWD=$pwd): $!"); if (my @st_pwd = stat($pwd)) { - my @st_cwd = stat($cwd) or die "stat($cwd): $!"; + my @st_cwd = stat($xcwd) or die "stat($xcwd): $!"; "@st_pwd[1,0]" eq "@st_cwd[1,0]" or - $self->{env}->{PWD} = $pwd = $cwd; + $self->{env}->{PWD} = $pwd = undef; } else { # PWD was invalid - delete $self->{env}->{PWD}; - undef $pwd; + $self->{env}->{PWD} = $pwd = undef; } } - $pwd //= $self->{env}->{PWD} = getcwd() // die "getcwd(PWD=$pwd): $!"; + $pwd //= $self->{env}->{PWD} = $cwd // getcwd() // die "getcwd: $!"; File::Spec->rel2abs($p, $pwd); } -sub store_path ($) { +sub share_path ($) { # $HOME/.local/share/lei/$FOO my ($self) = @_; rel2abs($self, ($self->{env}->{XDG_DATA_HOME} // ($self->{env}->{HOME} // '/nonexistent').'/.local/share') - .'/lei/store'); + .'/lei'); } +sub store_path ($) { share_path($_[0]) . '/store' } + sub _config_path ($) { my ($self) = @_; rel2abs($self, ($self->{env}->{XDG_CONFIG_HOME} // @@ -131,11 +134,14 @@ our %CMD = ( # sorted in order of importance/use: 'q' => [ '--stdin|SEARCH_TERMS...', 'search for messages matching terms', 'stdin|', # /|\z/ must be first for lone dash @lxs_opt, - qw(save-as=s output|mfolder|o=s format|f=s dedupe|d=s threads|t+ + qw(save output|mfolder|o=s format|f=s dedupe|d=s threads|t+ sort|s=s reverse|r offset=i pretty jobs|j=s globoff|g augment|a import-before! lock=s@ rsyncable alert=s@ mua=s verbose|v+), @c_opt, opt_dash('limit|n=i', '[0-9]+') ], +'up' => [ 'OUTPUT|--all', 'update saved search', + qw(jobs|j=s lock=s@ alert=s@ mua=s verbose|v+ all:s), @c_opt ], + 'blob' => [ 'OID', 'show a git blob, reconstructing from mail if necessary', qw(git-dir=s@ cwd! verbose|v+ mail! oid-a|A=s path-a|a=s path-b|b=s), @lxs_opt, @c_opt ], @@ -152,16 +158,18 @@ our %CMD = ( # sorted in order of importance/use: 'exclude further results from a publicinbox|extindex', qw(prune), @c_opt ], -'ls-query' => [ '[FILTER...]', 'list saved search queries', - qw(name-only format|f=s), @c_opt ], -'rm-query' => [ 'QUERY_NAME', 'remove a saved search', @c_opt ], -'mv-query' => [ qw(OLD_NAME NEW_NAME), 'rename a saved search', @c_opt ], +'ls-search' => [ '[PREFIX]', 'list saved search queries', + qw(format|f=s pretty l ascii z|0), @c_opt ], +'forget-search' => [ 'OUTPUT', 'forget a saved search', + qw(verbose|v+), @c_opt ], +'edit-search' => [ 'OUTPUT', "edit saved search via `git config --edit'", + @c_opt ], 'plonk' => [ '--threads|--from=IDENT', 'exclude mail matching From: or threads from non-Message-ID searches', qw(stdin| threads|t from|f=s mid=s oid=s), @c_opt ], -'mark' => [ 'KEYWORDS...', - 'set/unset keywords on message(s)', +'tag' => [ 'KEYWORDS...', + 'set/unset keywords and/or labels on message(s)', qw(stdin| in-format|F=s input|i=s@ oid=s@ mid=s@), @c_opt, pass_through('-kw:foo for delete') ], 'forget' => [ '[--stdin|--oid=OID|--by-mid=MID]', @@ -172,15 +180,6 @@ our %CMD = ( # sorted in order of importance/use: 'remove imported messages from IMAP, Maildirs, and MH', qw(exact! all jobs:i indexed), @c_opt ], -# code repos are used for `show' to solve blobs from patch mails -'add-coderepo' => [ 'DIRNAME', 'add or set priority of a git code repo', - qw(boost=i), @c_opt ], -'ls-coderepo' => [ '[FILTER_TERMS...]', - 'list known code repos', qw(format|f=s z), @c_opt ], -'forget-coderepo' => [ 'DIRNAME', - 'stop using repo to solve blobs from patches', - qw(prune), @c_opt ], - 'add-watch' => [ 'LOCATION', 'watch for new messages and flag changes', qw(import! kw|keywords|flags! interval=s recursive|r exclude=s include=s), @c_opt ], @@ -258,7 +257,7 @@ my %OPTDESC = ( 'torsocks=s' => ['VAL|auto|no|yes', 'whether or not to wrap git and curl commands with torsocks'], 'no-torsocks' => 'alias for --torsocks=no', -'save-as=s' => ['NAME', 'save a search terms by given name'], +'save' => "save a search for `lei up'", 'import-remote!' => 'do not memoize remote messages into local store', 'type=s' => [ 'any|mid|git', 'disambiguate type' ], @@ -316,7 +315,9 @@ my %OPTDESC = ( 'jobs|j=i add-external' => 'set parallelism when indexing after --mirror', 'in-format|F=s' => $stdin_formats, -'format|f=s ls-query' => $ls_format, +'format|f=s ls-search' => ['OUT|json|jsonl|concatjson', + 'listing output format' ], +'l ls-search' => 'long listing format', 'format|f=s ls-external' => $ls_format, 'limit|n=i@' => ['NUM', 'limit on number of matches (default: 10000)' ], @@ -333,6 +334,8 @@ my %OPTDESC = ( 'remote' => 'limit operations to those requiring network access', 'remote!' => 'prevent operations requiring network access', +'all:s up' => ['local', 'update all (local) saved searches' ], + 'mid=s' => 'specify the Message-ID of a message', 'oid=s' => 'specify the git object ID of a message', @@ -357,7 +360,12 @@ my %CONFIG_KEYS = ( 'leistore.dir' => 'top-level storage location', ); -my @WQ_KEYS = qw(lxs l2m imp mrr cnv p2q mark sol); # internal workers +my @WQ_KEYS = qw(lxs l2m imp mrr cnv p2q tag sol lsss); # internal workers + +sub _drop_wq { + my ($self) = @_; + for my $wq (grep(defined, delete(@$self{@WQ_KEYS}))) { $wq->DESTROY } +} # pronounced "exit": x_it(1 << 8) => exit(1); x_it(13) => SIGPIPE sub x_it ($$) { @@ -369,10 +377,7 @@ sub x_it ($$) { send($s, "x_it $code", MSG_EOR); } elsif ($self->{oneshot}) { # don't want to end up using $? from child processes - for my $f (@WQ_KEYS) { - my $wq = delete $self->{$f} or next; - $wq->DESTROY; - } + _drop_wq($self); # cleanup anything that has tempfiles or open file handles %PATH2CFG = (); delete @$self{qw(ovv dedupe sto cfg)}; @@ -401,11 +406,8 @@ sub qerr ($;@) { $_[0]->{opt}->{quiet} or err(shift, @_) } sub fail_handler ($;$$) { my ($lei, $code, $io) = @_; - for my $f (@WQ_KEYS) { - my $wq = delete $lei->{$f} or next; - $wq->wq_wait_old(undef, $lei) if $wq->wq_kill_old; # lei-daemon - } close($io) if $io; # needed to avoid warnings on SIGPIPE + _drop_wq($lei); x_it($lei, $code // (1 << 8)); } @@ -703,8 +705,7 @@ sub _lei_cfg ($;$) { $cur_st = pack('dd', $st[10], $st[7]); qerr($self, "# $f created") if $self->{cmd} ne 'config'; } - my $cfg = PublicInbox::Config::git_config_dump($f); - bless $cfg, 'PublicInbox::Config'; + my $cfg = PublicInbox::Config->git_config_dump($f); $cfg->{-st} = $cur_st; $cfg->{'-f'} = $f; if ($sto && File::Spec->canonpath($sto_dir // store_path($self)) @@ -845,14 +846,27 @@ sub start_mua { @cmd = map { $_ eq '%f' ? ($replaced = $mfolder) : $_ } @cmd; } push @cmd, $mfolder unless defined($replaced); - if (my $sock = $self->{sock}) { # lei(1) client process runs it - send($sock, exec_buf(\@cmd, {}), MSG_EOR); + if ($self->{sock}) { # lei(1) client process runs it + # restore terminal: echo $query | lei q -stdin --mua=... + my $io = []; + $io->[0] = $self->{1} if $self->{opt}->{stdin} && -t $self->{1}; + send_exec_cmd($self, $io, \@cmd, {}); } elsif ($self->{oneshot}) { - $self->{"pid.$self.$$"}->{spawn(\@cmd)} = \@cmd; + my $pid = fork // die "fork: $!"; + if ($pid > 0) { # original process + if ($self->{opt}->{stdin} && -t STDOUT) { + open STDIN, '+<&', \*STDOUT or die "dup2: $!"; + } + exec(@cmd); + warn "exec @cmd: $!\n"; + POSIX::_exit(1); + } + POSIX::setsid() > 0 or die "setsid: $!"; } if ($self->{lxs} && $self->{au_done}) { # kick wait_startq syswrite($self->{au_done}, 'q' x ($self->{lxs}->{jobs} // 0)); } + return unless -t $self->{2}; # XXX how to determine non-TUI MUAs? $self->{opt}->{quiet} = 1; delete $self->{-progress}; delete $self->{opt}->{verbose}; @@ -986,14 +1000,7 @@ sub accept_dispatch { # Listener {post_accept} callback sub dclose { my ($self) = @_; delete $self->{-progress}; - for my $f (@WQ_KEYS) { - my $wq = delete $self->{$f} or next; - if ($wq->wq_kill) { - $wq->wq_close(0, undef, $self); - } elsif ($wq->wq_kill_old) { - $wq->wq_wait_old(undef, $self); - } - } + _drop_wq($self); close(delete $self->{1}) if $self->{1}; # may reap_compress $self->close if $self->{-event_init_done}; # PublicInbox::DS::close }