]> Sergey Matveev's repositories - public-inbox.git/blobdiff - lib/PublicInbox/KQNotify.pm
imap+nntp: share COMPRESS implementation
[public-inbox.git] / lib / PublicInbox / KQNotify.pm
index 1b5c578ef3df8aef15a9adefab55c00c11ded432..7efb8b604c99e326d02c38c3876355aa23018a35 100644 (file)
@@ -1,16 +1,18 @@
-# Copyright (C) 2020 all contributors <meta@public-inbox.org>
+# Copyright (C) 2020-2021 all contributors <meta@public-inbox.org>
 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
 
 # implements the small subset of Linux::Inotify2 functionality we use
 # using IO::KQueue on *BSD systems.
 package PublicInbox::KQNotify;
 use strict;
+use v5.10.1;
 use IO::KQueue;
 use PublicInbox::DSKQXS; # wraps IO::KQueue for fork-safe DESTROY
+use PublicInbox::FakeInotify qw(fill_dirlist on_dir_change);
+use Time::HiRes qw(stat);
 
-# only true as far as public-inbox is concerned with .lock files:
-sub IN_CLOSE () { NOTE_WRITE }
-#sub IN_CLOSE () { 0x200 } # NOTE_CLOSE_WRITE (FreeBSD 11+ only)
+# NOTE_EXTEND detects rename(2), NOTE_WRITE detects link(2)
+sub MOVED_TO_OR_CREATE () { NOTE_EXTEND|NOTE_WRITE }
 
 sub new {
        my ($class) = @_;
@@ -18,20 +20,33 @@ sub new {
 }
 
 sub watch {
-       my ($self, $path, $mask, $cb) = @_;
-       open(my $fh, '<', $path) or return;
+       my ($self, $path, $mask) = @_;
+       my ($fh, $watch);
+       if (-d $path) {
+               opendir($fh, $path) or return;
+               my @st = stat($fh);
+               $watch = bless [ $fh, $path, $st[10] ],
+                       'PublicInbox::KQNotify::Watchdir';
+       } else {
+               open($fh, '<', $path) or return;
+               $watch = bless [ $fh, $path ],
+                       'PublicInbox::KQNotify::Watch';
+       }
        my $ident = fileno($fh);
-       $self->{dskq}->{kq}->EV_SET($ident, # ident
+       $self->{dskq}->{kq}->EV_SET($ident, # ident (fd)
                EVFILT_VNODE, # filter
                EV_ADD | EV_CLEAR, # flags
                $mask, # fflags
                0, 0); # data, udata
-       if ($mask == IN_CLOSE) {
-               $self->{watch}->{$ident} = [ $fh, $cb ];
+       if ($mask & (MOVED_TO_OR_CREATE|NOTE_DELETE|NOTE_LINK|NOTE_REVOKE)) {
+               $self->{watch}->{$ident} = $watch;
+               if ($mask & (NOTE_DELETE|NOTE_LINK|NOTE_REVOKE)) {
+                       fill_dirlist($self, $path, $fh)
+               }
        } else {
                die "TODO Not implemented: $mask";
        }
-       bless \$fh, 'PublicInbox::KQNotify::Watch';
+       $watch;
 }
 
 # emulate Linux::Inotify::fileno
@@ -45,22 +60,57 @@ sub on_overflow {}
 # noop for Linux::Inotify2 compatibility, we use `0' timeout for ->kevent
 sub blocking {}
 
-# behave like Linux::Inotify2::poll
-sub poll {
+# behave like Linux::Inotify2->read
+sub read {
        my ($self) = @_;
        my @kevents = $self->{dskq}->{kq}->kevent(0);
+       my $events = [];
+       my @gone;
+       my $watch = $self->{watch};
        for my $kev (@kevents) {
                my $ident = $kev->[KQ_IDENT];
                my $mask = $kev->[KQ_FFLAGS];
-               if (($mask & IN_CLOSE) == IN_CLOSE) {
-                       eval { $self->{watch}->{$ident}->[1]->() };
+               my ($dh, $path, $old_ctime) = @{$watch->{$ident}};
+               if (!defined($old_ctime)) {
+                       push @$events,
+                               bless(\$path, 'PublicInbox::FakeInotify::Event')
+               } elsif ($mask & (MOVED_TO_OR_CREATE|NOTE_DELETE|NOTE_LINK|
+                               NOTE_REVOKE|NOTE_RENAME)) {
+                       my @new_st = stat($path);
+                       if (!@new_st && $!{ENOENT}) {
+                               push @$events, bless(\$path,
+                                               'PublicInbox::FakeInotify::'.
+                                               'SelfGoneEvent');
+                               push @gone, $ident;
+                               delete $self->{dirlist}->{$path};
+                               next;
+                       }
+                       if (!@new_st) {
+                               warn "unhandled stat($path) error: $!\n";
+                               next;
+                       }
+                       $watch->{$ident}->[3] = $new_st[10]; # ctime
+                       rewinddir($dh);
+                       on_dir_change($events, $dh, $path, $old_ctime,
+                                       $self->{dirlist});
                }
        }
+       delete @$watch{@gone};
+       @$events;
 }
 
 package PublicInbox::KQNotify::Watch;
 use strict;
 
-sub cancel { close ${$_[0]} or die "close: $!" }
+sub name { $_[0]->[1] }
+
+sub cancel { close $_[0]->[0] or die "close: $!" }
+
+package PublicInbox::KQNotify::Watchdir;
+use strict;
+
+sub name { $_[0]->[1] }
+
+sub cancel { closedir $_[0]->[0] or die "closedir: $!" }
 
 1;