X-Git-Url: http://www.git.stargrave.org/?a=blobdiff_plain;f=lib%2FPublicInbox%2FLEI.pm;h=a258722e04d582724e567011aabd6c321e16c3a7;hb=b584a53f053a7629e656df00ccf6f168c260af48;hp=79dc9bf9972c7a23e89b9c0bd9fc0af87c09699e;hpb=d99020ac3612308d04e3760bd780417218748168;p=public-inbox.git diff --git a/lib/PublicInbox/LEI.pm b/lib/PublicInbox/LEI.pm index 79dc9bf9..a258722e 100644 --- a/lib/PublicInbox/LEI.pm +++ b/lib/PublicInbox/LEI.pm @@ -27,6 +27,7 @@ use PublicInbox::Eml; use Time::HiRes qw(stat); # ctime comparisons for config cache use File::Path qw(mkpath); use File::Spec; +use Sys::Syslog qw(openlog syslog closelog); our $quit = \&CORE::exit; our ($current_lei, $errors_log, $listener, $oldset, $dir_idle, $recv_cmd, $send_cmd); @@ -37,6 +38,7 @@ $GLP_PASS->configure(qw(gnu_getopt no_ignore_case auto_abbrev pass_through)); our %PATH2CFG; # persistent for socket daemon our $MDIR2CFGPATH; # /path/to/maildir => { /path/to/config => [ ino watches ] } +our %LIVE_SOCK; # "GLOB(0x....)" => $lei->{sock} # TBD: this is a documentation mechanism to show a subcommand # (may) pass options through to another command: @@ -277,8 +279,8 @@ our %CMD = ( # sorted in order of importance/use: 'git-config(1) wrapper for '._config_path($_[0]); }, qw(config-file|system|global|file|f=s), # for conflict detection qw(c=s@ C=s@), pass_through('git config') ], -'inspect' => [ 'ITEMS...', 'inspect lei/store and/or local external', - qw(pretty ascii dir=s), @c_opt ], +'inspect' => [ 'ITEMS...|--stdin', 'inspect lei/store and/or local external', + qw(stdin| pretty ascii dir=s), @c_opt ], 'init' => [ '[DIRNAME]', sub { "initialize storage, default: ".store_path($_[0]); @@ -463,7 +465,6 @@ sub x_it ($$) { my ($self, $code) = @_; # make sure client sees stdout before exit $self->{1}->autoflush(1) if $self->{1}; - dump_and_clear_log(); stop_pager($self); if ($self->{pkt_op_p}) { # to top lei-daemon $self->{pkt_op_p}->pkt_do('x_it', $code); @@ -485,6 +486,12 @@ sub err ($;@) { sub qerr ($;@) { $_[0]->{opt}->{quiet} or err(shift, @_) } +sub qfin { # show message on finalization (LeiFinmsg) + my ($lei, $msg) = @_; + return if $lei->{opt}->{quiet}; + $lei->{fmsg} ? push(@{$lei->{fmsg}}, "$msg\n") : qerr($lei, $msg); +} + sub fail_handler ($;$$) { my ($lei, $code, $io) = @_; close($io) if $io; # needed to avoid warnings on SIGPIPE @@ -525,6 +532,7 @@ sub puts ($;@) { out(shift, map { "$_\n" } @_) } sub child_error { # passes non-fatal curl exit codes to user my ($self, $child_error, $msg) = @_; # child_error is $? + $child_error ||= 1 << 8; $self->err($msg) if $msg; if ($self->{pkt_op_p}) { # to top lei-daemon $self->{pkt_op_p}->pkt_do('child_error', $child_error); @@ -565,6 +573,7 @@ sub _lei_atfork_child { $dir_idle->force_close if $dir_idle; %PATH2CFG = (); $MDIR2CFGPATH = {}; + %LIVE_SOCK = (); eval 'no warnings; undef $PublicInbox::LeiNoteEvent::to_flush'; undef $errors_log; $quit = \&CORE::exit; @@ -596,16 +605,19 @@ sub incr { $self->{counters}->{$field} += $nr; } +sub pkt_ops { + my ($lei, $ops) = @_; + $ops->{'!'} = [ \&fail_handler, $lei ]; + $ops->{'|'} = [ \&sigpipe_handler, $lei ]; + $ops->{x_it} = [ \&x_it, $lei ]; + $ops->{child_error} = [ \&child_error, $lei ]; + $ops->{incr} = [ \&incr, $lei ]; + $ops; +} + sub workers_start { my ($lei, $wq, $jobs, $ops, $flds) = @_; - $ops = { - '!' => [ \&fail_handler, $lei ], - '|' => [ \&sigpipe_handler, $lei ], - 'x_it' => [ \&x_it, $lei ], - 'child_error' => [ \&child_error, $lei ], - 'incr' => [ \&incr, $lei ], - ($ops ? %$ops : ()), - }; + $ops = pkt_ops($lei, { ($ops ? %$ops : ()) }); $ops->{''} //= [ $wq->can('_lei_wq_eof') || \&wq_eof, $lei ]; my $end = $lei->pkt_op_pair; my $ident = $wq->{-wq_ident} // "lei-$lei->{cmd} worker"; @@ -757,7 +769,6 @@ sub dispatch { my ($self, $cmd, @argv) = @_; local $current_lei = $self; # for __WARN__ $self->{2}->autoflush(1); # keep stdout buffered until x_it|DESTROY - dump_and_clear_log("from previous run\n"); return _help($self, 'no command given') unless defined($cmd); # do not support Getopt bundling for this while ($cmd eq '-C' || $cmd eq '-c') { @@ -943,6 +954,9 @@ sub exec_buf ($$) { sub start_mua { my ($self) = @_; + if ($self->{ovv}->{fmt} =~ /\A(?:maildir)\z/) { # TODO: IMAP + refresh_watches($self); + } my $mua = $self->{opt}->{mua} // return; my $mfolder = $self->{ovv}->{dst}; my (@cmd, $replaced); @@ -1136,10 +1150,12 @@ sub oldset { $oldset } sub dump_and_clear_log { if (defined($errors_log) && -s STDIN && seek(STDIN, 0, SEEK_SET)) { - my @pfx = @_; - unshift(@pfx, "$errors_log ") if @pfx; - warn @pfx, do { local $/; }; - truncate(STDIN, 0) or warn "ftruncate ($errors_log): $!"; + openlog('lei-daemon', 'pid,nowait,nofatal,ndelay', 'user'); + chomp(my @lines = ); + truncate(STDIN, 0) or + syslog('warning', "ftruncate (%s): %m", $errors_log); + for my $l (@lines) { syslog('warning', '%s', $l) } + closelog(); # don't share across fork } } @@ -1232,7 +1248,7 @@ sub lazy_start { (-p STDOUT) or die "E: stdout must be a pipe\n"; open(STDIN, '+>>', $errors_log) or die "open($errors_log): $!"; STDIN->autoflush(1); - dump_and_clear_log("from previous daemon process:\n"); + dump_and_clear_log(); POSIX::setsid() > 0 or die "setsid: $!"; my $pid = fork // die "fork: $!"; return if $pid; @@ -1246,6 +1262,7 @@ sub lazy_start { my (undef, $eof_p) = PublicInbox::PktOp->pair; sub { $exit_code //= shift; + eval 'PublicInbox::LeiNoteEvent::flush_task()'; my $lis = $pil or exit($exit_code); # closing eof_p triggers \&noop wakeup $listener = $eof_p = $pil = $path = undef; @@ -1316,6 +1333,7 @@ sub lazy_start { open STDOUT, '>&STDIN' or die "redirect stdout failed: $!"; # $daemon pipe to `lei' closed, main loop begins: PublicInbox::DS->EventLoop; + dump_and_clear_log(); exit($exit_code // 0); } @@ -1328,11 +1346,12 @@ sub DESTROY { if (my $counters = delete $self->{counters}) { for my $k (sort keys %$counters) { my $nr = $counters->{$k}; - $self->child_error(1 << 8, "$nr $k messages"); + $self->child_error(0, "$nr $k messages"); } } $self->{1}->autoflush(1) if $self->{1}; stop_pager($self); + dump_and_clear_log(); # preserve $? for ->fail or ->x_it code } @@ -1370,6 +1389,14 @@ sub cancel_maildir_watch ($$) { for my $x (@{$w // []}) { $x->cancel } } +sub add_maildir_watch ($$) { + my ($d, $cfg_f) = @_; + if (!exists($MDIR2CFGPATH->{$d}->{$cfg_f})) { + my @w = $dir_idle->add_watches(["$d/cur", "$d/new"], 1); + push @{$MDIR2CFGPATH->{$d}->{$cfg_f}}, @w if @w; + } +} + sub refresh_watches { my ($lei) = @_; my $cfg = _lei_cfg($lei) or return; @@ -1380,7 +1407,7 @@ sub refresh_watches { for my $w (grep(/\Awatch\..+\.state\z/, keys %$cfg)) { my $url = substr($w, length('watch.'), -length('.state')); require PublicInbox::LeiWatch; - my $lw = $watches->{$url} //= PublicInbox::LeiWatch->new($url); + $watches->{$url} //= PublicInbox::LeiWatch->new($url); $seen{$url} = undef; my $state = $cfg->get_1("watch.$url", 'state'); if (!watch_state_ok($state)) { @@ -1391,16 +1418,37 @@ sub refresh_watches { my $d = canonpath_harder($1); if ($state eq 'pause') { cancel_maildir_watch($d, $cfg_f); - } elsif (!exists($MDIR2CFGPATH->{$d}->{$cfg_f})) { - my @w = $dir_idle->add_watches( - ["$d/cur", "$d/new"], 1); - push @{$MDIR2CFGPATH->{$d}->{$cfg_f}}, @w if @w; + } else { + add_maildir_watch($d, $cfg_f); } } else { # TODO: imap/nntp/jmap - $lei->child_error(1, - "E: watch $url not supported, yet"); + $lei->child_error(0, "E: watch $url not supported, yet") + } + } + + # add all known Maildir folders as implicit watches + my $sto = $lei->_lei_store; + my $renames = 0; + if (my $lms = $sto ? $sto->search->lms : undef) { + 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; + } + next if $watches->{$f}; # may be set to pause + require PublicInbox::LeiWatch; + $watches->{$f} = PublicInbox::LeiWatch->new($f); + $seen{$f} = undef; + 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}; @@ -1409,7 +1457,7 @@ sub refresh_watches { my $d = canonpath_harder($1); cancel_maildir_watch($d, $cfg_f); } else { # TODO: imap/nntp/jmap - $lei->child_error(1, "E: watch $url TODO"); + $lei->child_error(0, "E: watch $url TODO"); } } } @@ -1434,4 +1482,19 @@ sub lms { # read-only LeiMailSync $lse ? $lse->lms : 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'); + } +} + +sub sto_done_complete { # called in lei-daemon when LeiStore->done is complete + my ($sock_str) = @_; + delete $LIVE_SOCK{$sock_str}; # frees {sock} for waiting lei clients +} + 1;