]> Sergey Matveev's repositories - public-inbox.git/blobdiff - lib/PublicInbox/LEI.pm
lei: close inotify FD in forked child
[public-inbox.git] / lib / PublicInbox / LEI.pm
index b92d75125df4c9578c5475083a0c79e348b8d3cb..e6f763e1067869e0a7c6d3bb8a90e2b544f45e0d 100644 (file)
@@ -36,7 +36,7 @@ my $GLP_PASS = Getopt::Long::Parser->new;
 $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 => undef }
+our $MDIR2CFGPATH; # /path/to/maildir => { /path/to/config => [ ino watches ] }
 
 # TBD: this is a documentation mechanism to show a subcommand
 # (may) pass options through to another command:
@@ -134,7 +134,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)
 }
 
@@ -232,8 +232,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 ],
@@ -369,7 +371,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',
@@ -554,7 +556,7 @@ sub _lei_atfork_child {
        }
        close $listener if $listener;
        undef $listener;
-       undef $dir_idle;
+       $dir_idle->force_close if $dir_idle;
        %PATH2CFG = ();
        $MDIR2CFGPATH = {};
        eval 'no warnings; undef $PublicInbox::LeiNoteEvent::to_flush';
@@ -820,6 +822,8 @@ sub _lei_cfg ($;$) {
                }
        }
        $self->{cfg} = $PATH2CFG{$f} = $cfg;
+       refresh_watches($self);
+       $cfg;
 }
 
 sub _lei_store ($;$) {
@@ -1201,7 +1205,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): $!";
@@ -1353,36 +1356,62 @@ sub watch_state_ok ($) {
        $state =~ /\Apause|(?:import|index|tag)-(?:ro|rw)\z/;
 }
 
+sub cancel_maildir_watch ($$) {
+       my ($d, $cfg_f) = @_;
+       my $w = delete $MDIR2CFGPATH->{$d}->{$cfg_f};
+       scalar(keys %{$MDIR2CFGPATH->{$d}}) or
+               delete $MDIR2CFGPATH->{$d};
+       for my $x (@{$w // []}) { $x->cancel }
+}
+
 sub refresh_watches {
        my ($lei) = @_;
        my $cfg = _lei_cfg($lei) or return;
-       $cfg->{-env} //= { %{$lei->{env}}, PWD => '/' }; # for cfg2lei
+       my $old = $cfg->{-watches};
        my $watches = $cfg->{-watches} //= {};
-       require PublicInbox::LeiWatch;
+       my %seen;
+       my $cfg_f = $cfg->{'-f'};
        for my $w (grep(/\Awatch\..+\.state\z/, keys %$cfg)) {
                my $url = substr($w, length('watch.'), -length('.state'));
-               my $lw = $watches->{$w} //= PublicInbox::LeiWatch->new($url);
+               require PublicInbox::LeiWatch;
+               my $lw = $watches->{$url} //= PublicInbox::LeiWatch->new($url);
+               $seen{$url} = undef;
                my $state = $cfg->get_1("watch.$url", 'state');
                if (!watch_state_ok($state)) {
                        $lei->err("watch.$url.state=$state not supported");
                        next;
                }
-               my $f = $cfg->{'-f'};
                if ($url =~ /\Amaildir:(.+)/i) {
                        my $d = File::Spec->canonpath($1);
                        if ($state eq 'pause') {
-                               delete $MDIR2CFGPATH->{$d}->{$f};
-                               scalar(keys %{$MDIR2CFGPATH->{$d}}) or
-                                       delete $MDIR2CFGPATH->{$d};
-                       } elsif (!exists($MDIR2CFGPATH->{$d}->{$f})) {
-                               $dir_idle->add_watches(["$d/cur", "$d/new"], 1);
-                               $MDIR2CFGPATH->{$d}->{$f} = undef;
+                               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 { # TODO: imap/nntp/jmap
                        $lei->child_error(1,
                                "E: watch $url not supported, yet");
                }
        }
+       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);
+                               cancel_maildir_watch($d, $cfg_f);
+                       } else { # TODO: imap/nntp/jmap
+                               $lei->child_error(1, "E: watch $url TODO");
+                       }
+               }
+       }
+       if (scalar keys %$watches) {
+               $cfg->{-env} //= { %{$lei->{env}}, PWD => '/' }; # for cfg2lei
+       } else {
+               delete $cfg->{-watches};
+       }
 }
 
 1;