X-Git-Url: http://www.git.stargrave.org/?a=blobdiff_plain;f=lib%2FPublicInbox%2FLEI.pm;h=28fe0c836d7a3e842217b9bfd3cf63ac8ece2bf8;hb=b6eb866869609afef72c77f41507905828014673;hp=52c551cf998b7561d3b4a9cec57aa957a9502b3f;hpb=a1539672716f91acc67397a1a7d16e99df068931;p=public-inbox.git diff --git a/lib/PublicInbox/LEI.pm b/lib/PublicInbox/LEI.pm index 52c551cf..28fe0c83 100644 --- a/lib/PublicInbox/LEI.pm +++ b/lib/PublicInbox/LEI.pm @@ -37,6 +37,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: @@ -94,6 +95,12 @@ sub rel2abs { # abs_path resolves symlinks in parent iff all parents exist sub abs_path { Cwd::abs_path($_[1]) // rel2abs(@_) } +sub canonpath_harder { + my $p = $_[-1]; # $_[0] may be self + $p = File::Spec->canonpath($p); + $p =~ m!(?:/*|\A)\.\.(?:/*|\z)! && -e $p ? Cwd::abs_path($p) : $p; +} + sub share_path ($) { # $HOME/.local/share/lei/$FOO my ($self) = @_; rel2abs($self, ($self->{env}->{XDG_DATA_HOME} // @@ -134,7 +141,7 @@ sub ale { sub index_opt { # TODO: drop underscore variants everywhere, they're undocumented qw(fsync|sync! jobs|j=i indexlevel|L=s compact - max_size|max-size=s sequential_shard|sequential-shard + max_size|max-size=s sequential-shard batch_size|batch-size=s skip-docdata) } @@ -167,12 +174,12 @@ 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 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+ shared color! mail-sync!), @c_opt, opt_dash('limit|n=i', '[0-9]+') ], -'up' => [ 'OUTPUT|--all', 'update saved search', +'up' => [ 'OUTPUT...|--all', 'update saved search', qw(jobs|j=s lock=s@ alert=s@ mua=s verbose|v+ all:s), @c_opt ], 'lcat' => [ '--stdin|MSGID_OR_URL...', 'display local copy of message(s)', @@ -232,8 +239,10 @@ our %CMD = ( # sorted in order of importance/use: 'remove imported messages from IMAP, Maildirs, and MH', qw(exact! all jobs:i indexed), @c_opt ], -'add-watch' => [ 'LOCATION', 'watch for new messages and flag changes', +'add-watch' => [ 'LOCATION...', 'watch for new messages and flag changes', qw(poll-interval=s state=s recursive|r), @c_opt ], +'rm-watch' => [ 'LOCATION...', 'remove specified watch(es)', + qw(recursive|r), @c_opt ], 'ls-watch' => [ '[FILTER...]', 'list active watches with numbers and status', qw(l z|0), @c_opt ], 'pause-watch' => [ '[WATCH_NUMBER_OR_FILTER]', qw(all local remote), @c_opt ], @@ -329,7 +338,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' => "save a search for `lei up'", +'save!' => "do not save a search for `lei up'", 'import-remote!' => 'do not memoize remote messages into local store', 'type=s' => [ 'any|mid|git', 'disambiguate type' ], @@ -369,7 +378,7 @@ my %OPTDESC = ( 'do not index messages larger than SIZE (default: infinity)' ], 'batch_size|batch-size=s' => [ 'SIZE', 'flush changes to OS after given number of bytes (default: 1m)' ], -'sequential_shard|sequential-shard' => +'sequential-shard' => 'index Xapian shards sequentially for slow storage', 'skip-docdata' => 'drop compatibility w/ public-inbox <1.6 to save ~1.5% space', @@ -477,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 @@ -554,9 +569,10 @@ sub _lei_atfork_child { } close $listener if $listener; undef $listener; - undef $dir_idle; + $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; @@ -796,7 +812,7 @@ sub _lei_cfg ($;$) { delete $self->{cfg}; return bless {}, 'PublicInbox::Config'; } - my (undef, $cfg_dir, undef) = File::Spec->splitpath($f); + my ($cfg_dir) = ($f =~ m!(.*?/)[^/]+\z!); -d $cfg_dir or mkpath($cfg_dir) or die "mkpath($cfg_dir): $!\n"; open my $fh, '>>', $f or die "open($f): $!\n"; @st = stat($fh) or die "fstat($f): $!\n"; @@ -806,8 +822,8 @@ sub _lei_cfg ($;$) { 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)) - eq File::Spec->canonpath($cfg->{'leistore.dir'} // + if ($sto && canonpath_harder($sto_dir // store_path($self)) + eq canonpath_harder($cfg->{'leistore.dir'} // store_path($self))) { $cfg->{-lei_store} = $sto; $cfg->{-lei_note_event} = $lne; @@ -1203,7 +1219,6 @@ sub lazy_start { } umask(077) // die("umask(077): $!"); bind($listener, $addr) or die "bind($path): $!"; - listen($listener, 1024) or die "listen: $!"; $lk->lock_release; undef $lk; my @st = stat($path) or die "stat($path): $!"; @@ -1363,6 +1378,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; @@ -1373,7 +1396,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)) { @@ -1381,25 +1404,46 @@ sub refresh_watches { next; } if ($url =~ /\Amaildir:(.+)/i) { - my $d = File::Spec->canonpath($1); + 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(1, "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}; delete $old->{$url}; if ($url =~ /\Amaildir:(.+)/i) { - my $d = File::Spec->canonpath($1); + my $d = canonpath_harder($1); cancel_maildir_watch($d, $cfg_f); } else { # TODO: imap/nntp/jmap $lei->child_error(1, "E: watch $url TODO"); @@ -1413,4 +1457,33 @@ sub refresh_watches { } } +sub git_blob_id { + my ($lei, $eml) = @_; + ($lei->{sto} // _lei_store($lei, 1))->git_blob_id($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 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;