]> Sergey Matveev's repositories - public-inbox.git/blobdiff - lib/PublicInbox/KQNotify.pm
kqnotify|fake_inotify: detect Maildir write ops
[public-inbox.git] / lib / PublicInbox / KQNotify.pm
index 110594cc02c0332695831ebe2e3a273ee2da10bd..9673b44290a991ea1853c8642c10c326c5dd779e 100644 (file)
@@ -7,6 +7,11 @@ package PublicInbox::KQNotify;
 use strict;
 use IO::KQueue;
 use PublicInbox::DSKQXS; # wraps IO::KQueue for fork-safe DESTROY
+use PublicInbox::FakeInotify;
+use Time::HiRes qw(stat);
+
+# NOTE_EXTEND detects rename(2), NOTE_WRITE detects link(2)
+sub MOVED_TO_OR_CREATE () { NOTE_EXTEND|NOTE_WRITE }
 
 sub new {
        my ($class) = @_;
@@ -15,19 +20,28 @@ sub new {
 
 sub watch {
        my ($self, $path, $mask, $cb) = @_;
-       open(my $fh, '<', $path) or return;
+       my ($fh, $cls, @extra);
+       if (-d $path) {
+               opendir($fh, $path) or return;
+               my @st = stat($fh);
+               @extra = ($path, $st[10]); # 10: ctime
+               $cls = 'PublicInbox::KQNotify::Watchdir';
+       } else {
+               open($fh, '<', $path) or return;
+               $cls = 'PublicInbox::KQNotify::Watch';
+       }
        my $ident = fileno($fh);
        $self->{dskq}->{kq}->EV_SET($ident, # ident
                EVFILT_VNODE, # filter
                EV_ADD | EV_CLEAR, # flags
                $mask, # fflags
                0, 0); # data, udata
-       if ($mask == NOTE_WRITE) {
-               $self->{watch}->{$ident} = [ $fh, $cb ];
+       if ($mask == NOTE_WRITE || $mask == MOVED_TO_OR_CREATE) {
+               $self->{watch}->{$ident} = [ $fh, $cb, @extra ];
        } else {
                die "TODO Not implemented: $mask";
        }
-       bless \$fh, 'PublicInbox::KQNotify::Watch';
+       bless \$fh, $cls;
 }
 
 # emulate Linux::Inotify::fileno
@@ -48,8 +62,15 @@ sub poll {
        for my $kev (@kevents) {
                my $ident = $kev->[KQ_IDENT];
                my $mask = $kev->[KQ_FFLAGS];
-               if (($mask & NOTE_WRITE) == NOTE_WRITE) {
-                       eval { $self->{watch}->{$ident}->[1]->() };
+               my ($dh, $cb, $path, $old_ctime) = @{$self->{watch}->{$ident}};
+               if (!defined($path) && ($mask & NOTE_WRITE) == NOTE_WRITE) {
+                       eval { $cb->() };
+               } elsif ($mask & MOVED_TO_OR_CREATE) {
+                       my @new_st = stat($path) or next;
+                       $self->{watch}->{$ident}->[3] = $new_st[10]; # ctime
+                       rewinddir($dh);
+                       PublicInbox::FakeInotify::on_new_files($dh, $cb,
+                                                       $path, $old_ctime);
                }
        }
 }
@@ -59,4 +80,9 @@ use strict;
 
 sub cancel { close ${$_[0]} or die "close: $!" }
 
+package PublicInbox::KQNotify::Watchdir;
+use strict;
+
+sub cancel { closedir ${$_[0]} or die "closedir: $!" }
+
 1;