X-Git-Url: http://www.git.stargrave.org/?a=blobdiff_plain;f=lib%2FPublicInbox%2FLEI.pm;h=053b6174d808514f04227b32f3bef982e72b3498;hb=47d4e53734820b4e;hp=bbb6ab7e439c0f6332fba41d1115b7af53f70833;hpb=c1b44ad0a8901a488063c35eb2fe81cd6e051933;p=public-inbox.git diff --git a/lib/PublicInbox/LEI.pm b/lib/PublicInbox/LEI.pm index bbb6ab7e..053b6174 100644 --- a/lib/PublicInbox/LEI.pm +++ b/lib/PublicInbox/LEI.pm @@ -24,6 +24,8 @@ use PublicInbox::DS qw(now dwaitpid); use PublicInbox::Spawn qw(spawn popen_rd); use PublicInbox::Lock; use PublicInbox::Eml; +use PublicInbox::Import; +use PublicInbox::ContentHash qw(git_sha); use Time::HiRes qw(stat); # ctime comparisons for config cache use File::Path qw(mkpath); use File::Spec; @@ -256,12 +258,12 @@ our %CMD = ( # sorted in order of importance/use: @c_opt ], 'import' => [ 'LOCATION...|--stdin', 'one-time import/update from URL or filesystem', - qw(stdin| offset=i recursive|r exclude=s include|I=s jobs=s new-only + qw(stdin| offset=i recursive|r exclude=s include|I=s new-only lock=s@ in-format|F=s kw! verbose|v+ incremental! mail-sync!), @net_opt, @c_opt ], 'forget-mail-sync' => [ 'LOCATION...', 'forget sync information for a mail folder', @c_opt ], -'prune-mail-sync' => [ 'LOCATION...|--all', +'refresh-mail-sync' => [ 'LOCATION...|--all', 'prune dangling sync data for a mail folder', 'all:s', @c_opt ], 'export-kw' => [ 'LOCATION...|--all', 'one-time export of keywords of sync sources', @@ -464,10 +466,12 @@ sub x_it ($$) { # make sure client sees stdout before exit $self->{1}->autoflush(1) if $self->{1}; stop_pager($self); - if ($self->{pkt_op_p}) { # to top lei-daemon + if ($self->{pkt_op_p}) { # worker => lei-daemon $self->{pkt_op_p}->pkt_do('x_it', $code); - } elsif ($self->{sock}) { # to lei(1) client + } elsif ($self->{sock}) { # lei->daemon => lei(1) client send($self->{sock}, "x_it $code", MSG_EOR); + } elsif ($quit == \&CORE::exit) { # an admin (one-shot) command + exit($code >> 8); } # else ignore if client disconnected } @@ -511,7 +515,7 @@ sub fail ($$;$) { my ($self, $buf, $exit_code) = @_; $self->{failed}++; err($self, $buf) if defined $buf; - # calls fail_handler: + # calls fail_handler $self->{pkt_op_p}->pkt_do('!') if $self->{pkt_op_p}; x_it($self, ($exit_code // 1) << 8); undef; @@ -536,6 +540,8 @@ sub child_error { # passes non-fatal curl exit codes to user $self->{pkt_op_p}->pkt_do('child_error', $child_error); } elsif ($self->{sock}) { # to lei(1) client send($self->{sock}, "child_error $child_error", MSG_EOR); + } else { # non-lei admin command + $self->{child_error} ||= $child_error; } # else noop if client disconnected } @@ -610,6 +616,7 @@ sub pkt_ops { $ops->{x_it} = [ \&x_it, $lei ]; $ops->{child_error} = [ \&child_error, $lei ]; $ops->{incr} = [ \&incr, $lei ]; + $ops->{sto_done_request} = [ \&sto_done_request, $lei, $lei->{sock} ]; $ops; } @@ -620,6 +627,7 @@ sub workers_start { my $end = $lei->pkt_op_pair; my $ident = $wq->{-wq_ident} // "lei-$lei->{cmd} worker"; $flds->{lei} = $lei; + $wq->{-wq_nr_workers} //= $jobs; # lock, no incrementing $wq->wq_workers_start($ident, $jobs, $lei->oldset, $flds); delete $lei->{pkt_op_p}; my $op_c = delete $lei->{pkt_op_c}; @@ -765,6 +773,8 @@ sub lazy_cb ($$$) { sub dispatch { my ($self, $cmd, @argv) = @_; + fchdir($self) or return; + local %ENV = %{$self->{env}}; local $current_lei = $self; # for __WARN__ $self->{2}->autoflush(1); # keep stdout buffered until x_it|DESTROY return _help($self, 'no command given') unless defined($cmd); @@ -820,7 +830,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); + my $cfg = PublicInbox::Config->git_config_dump($f, $self->{2}); $cfg->{-st} = $cur_st; $cfg->{'-f'} = $f; if ($sto && canonpath_harder($sto_dir // store_path($self)) @@ -1029,13 +1039,15 @@ sub path_to_fd { # caller needs to "-t $self->{1}" to check if tty sub start_pager { - my ($self) = @_; + my ($self, $new_env) = @_; my $fh = popen_rd([qw(git var GIT_PAGER)]); chomp(my $pager = <$fh> // ''); close($fh) or warn "`git var PAGER' error: \$?=$?"; return if $pager eq 'cat' || $pager eq ''; - my $new_env = { LESS => 'FRX', LV => '-c' }; - $new_env->{MORE} = 'FRX' if $^O eq 'freebsd'; + $new_env //= {}; + $new_env->{LESS} //= 'FRX'; + $new_env->{LV} //= '-c'; + $new_env->{MORE} = $new_env->{LESS} if $^O eq 'freebsd'; pipe(my ($r, $wpager)) or return warn "pipe: $!"; my $rdr = { 0 => $r, 1 => $self->{1}, 2 => $self->{2} }; my $pgr = [ undef, @$rdr{1, 2} ]; @@ -1052,12 +1064,23 @@ sub start_pager { $self->{pgr} = $pgr; } +# display a message for user before spawning full-screen $VISUAL +sub pgr_err { + my ($self, @msg) = @_; + return $self->err(@msg) unless $self->{sock} && -t $self->{2}; + start_pager($self, { LESS => 'RX' }); # no 'F' so we prompt + print { $self->{2} } @msg; + $self->{2}->autoflush(1); + stop_pager($self); + send($self->{sock}, 'wait', MSG_EOR); # wait for user to quit pager +} + sub stop_pager { my ($self) = @_; my $pgr = delete($self->{pgr}) or return; $self->{2} = $pgr->[2]; - # do not restore original stdout, just close it so we error out close(delete($self->{1})) if $self->{1}; + $self->{1} = $pgr->[1]; } sub accept_dispatch { # Listener {post_accept} callback @@ -1087,14 +1110,9 @@ sub accept_dispatch { # Listener {post_accept} callback my ($argc, @argv) = split(/\0/, $buf, -1); undef $buf; my %env = map { split(/=/, $_, 2) } splice(@argv, $argc); - if (chdir($self->{3})) { - local %ENV = %env; - $self->{env} = \%env; - eval { dispatch($self, @argv) }; - send($sock, $@, MSG_EOR) if $@; - } else { - send($sock, "fchdir: $!", MSG_EOR); # implicit close - } + $self->{env} = \%env; + eval { dispatch($self, @argv) }; + send($sock, $@, MSG_EOR) if $@; } sub dclose { @@ -1164,7 +1182,6 @@ sub cfg2lei ($) { open($lei->{1}, '>>&', \*STDOUT) or die "dup 1: $!"; open($lei->{2}, '>>&', \*STDERR) or die "dup 2: $!"; open($lei->{3}, '/') or die "open /: $!"; - chdir($lei->{3}) or die "chdir /': $!"; my ($x, $y); socketpair($x, $y, AF_UNIX, SOCK_SEQPACKET, 0) or die "socketpair: $!"; $lei->{sock} = $x; @@ -1182,7 +1199,6 @@ sub dir_idle_handler ($) { # PublicInbox::DirIdle callback for my $f (keys %{$MDIR2CFGPATH->{$mdir} // {}}) { my $cfg = $PATH2CFG{$f} // next; eval { - local %ENV = %{$cfg->{-env}}; my $lei = cfg2lei($cfg); $lei->dispatch('note-event', "maildir:$mdir", $nc, $bn, $fn); @@ -1330,7 +1346,8 @@ sub lazy_start { open STDERR, '>&STDIN' or die "redirect stderr failed: $!"; open STDOUT, '>&STDIN' or die "redirect stdout failed: $!"; # $daemon pipe to `lei' closed, main loop begins: - PublicInbox::DS->EventLoop; + eval { PublicInbox::DS->EventLoop }; + warn "event loop error: $@\n" if $@; dump_and_clear_log(); exit($exit_code // 0); } @@ -1425,20 +1442,16 @@ sub refresh_watches { } # add all known Maildir folders as implicit watches - my $sto = $lei->_lei_store; - my $renames = 0; - if (my $lms = $sto ? $sto->search->lms : undef) { + my $lms = $lei->lms; + if ($lms) { + $lms->lms_write_prepare; for my $d ($lms->folders('maildir:')) { substr($d, 0, length('maildir:')) = ''; - my $cd = canonpath_harder($d); - my $f = "maildir:$cd"; # fixup old bugs while we're iterating: - if ($d ne $cd) { - $sto->ipc_do('lms_rename_folder', - "maildir:$d", $f); - ++$renames; - } + my $cd = canonpath_harder($d); + my $f = "maildir:$cd"; + $lms->rename_folder("maildir:$d", $f) if $d ne $cd; next if $watches->{$f}; # may be set to pause require PublicInbox::LeiWatch; $watches->{$f} = PublicInbox::LeiWatch->new($f); @@ -1446,7 +1459,6 @@ sub refresh_watches { add_maildir_watch($cd, $cfg_f); } } - $lei->sto_done_request if $renames; if ($old) { # cull old non-existent entries for my $url (keys %$old) { next if exists $seen{$url}; @@ -1466,28 +1478,32 @@ sub refresh_watches { } } -sub git_blob_id { - my ($lei, $eml) = @_; - ($lei->{sto} // _lei_store($lei, 1))->git_blob_id($eml); +# TODO: support SHA-256 +sub git_oid { + my $eml = $_[-1]; + $eml->header_set($_) for @PublicInbox::Import::UNWANTED_HEADERS; + git_sha(1, $eml); } -sub lms { # read-only LeiMailSync - my ($lei) = @_; - my $lse = $lei->{lse} // do { - my $sto = $lei->{sto} // _lei_store($lei); - $sto ? $sto->search : undef - }; - $lse ? $lse->lms : undef; +sub lms { + my ($lei, $rw) = @_; + my $sto = $lei->{sto} // _lei_store($lei) // return; + require PublicInbox::LeiMailSync; + my $f = "$sto->{priv_eidx}->{topdir}/mail_sync.sqlite3"; + (-f $f || $rw) ? PublicInbox::LeiMailSync->new($f) : undef; } sub sto_done_request { # only call this from lei-daemon process (not workers) my ($lei, $sock) = @_; - if ($sock //= $lei->{sock}) { - $LIVE_SOCK{"$sock"} = $sock; - $lei->{sto}->ipc_do('done', "$sock"); # issue, async wait - } else { # forcibly wait - my $wait = $lei->{sto}->ipc_do('done'); - } + eval { + if ($sock //= $lei->{sock}) { # issue, async wait + $LIVE_SOCK{"$sock"} = $sock; + $lei->{sto}->ipc_do('done', "$sock"); + } else { # forcibly wait + my $wait = $lei->{sto}->ipc_do('done'); + } + }; + $lei->err($@) if $@; } sub sto_done_complete { # called in lei-daemon when LeiStore->done is complete