diff options
Diffstat (limited to 'lib/PublicInbox/TailNotify.pm')
-rw-r--r-- | lib/PublicInbox/TailNotify.pm | 97 |
1 files changed, 97 insertions, 0 deletions
diff --git a/lib/PublicInbox/TailNotify.pm b/lib/PublicInbox/TailNotify.pm new file mode 100644 index 00000000..84340a35 --- /dev/null +++ b/lib/PublicInbox/TailNotify.pm @@ -0,0 +1,97 @@ +# Copyright (C) all contributors <meta@public-inbox.org> +# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt> + +# only used for tests at the moment... +package PublicInbox::TailNotify; +use v5.12; +use parent qw(PublicInbox::DirIdle); # not optimal, maybe.. +use PublicInbox::DS qw(now); + +my ($TAIL_MOD, $ino_cls); +if ($^O eq 'linux' && eval { require PublicInbox::Inotify; 1 }) { + $TAIL_MOD = PublicInbox::Inotify::IN_MOVED_TO() | + PublicInbox::Inotify::IN_CREATE() | + PublicInbox::Inotify::IN_MODIFY(); + $ino_cls = 'PublicInbox::Inotify'; +} elsif (eval { require PublicInbox::KQNotify }) { + $TAIL_MOD = PublicInbox::KQNotify::MOVED_TO_OR_CREATE() | + IO::KQueue::NOTE_DELETE() | IO::KQueue::NOTE_RENAME(); + $ino_cls = 'PublicInbox::KQNotify'; +} else { + require PublicInbox::FakeInotify; + $TAIL_MOD = PublicInbox::FakeInotify::MOVED_TO_OR_CREATE() | + PublicInbox::FakeInotify::IN_MODIFY() | + PublicInbox::FakeInotify::IN_DELETE(); +} +require IO::Poll if $ino_cls; + +sub reopen_file ($) { + my ($self) = @_; + + open my $fh, '<', $self->{fn} or return undef; + my @st = stat $fh or die "fstat($self->{fn}): $!"; + $self->{ino_dev} = "@st[0, 1]"; + $self->{inot}->watch($self->{fn}, $TAIL_MOD); + $self->{watch_fh} = $fh; # return value +} + +sub new { + my ($cls, $fn) = @_; + my $self = bless { fn => $fn }, $cls; + if ($ino_cls) { + $self->{inot} = $ino_cls->new or die "E: $ino_cls->new: $!"; + $self->{inot}->blocking(0); + my ($dn) = ($fn =~ m!\A(.+)/+[^/]+\z!); + $self->{inot}->watch($dn // '.', $TAIL_MOD); + } else { + $self->{inot} = PublicInbox::FakeInotify->new; + } + reopen_file($self); + $self->{inot}->watch($fn, $TAIL_MOD); + $self; +} + +sub delete_self { + for (@_) { return 1 if $_->IN_DELETE_SELF } + undef; +} + +sub getlines { + my ($self, $timeo) = @_; + my ($fh, $buf, $rfds, @ret, @events); + my $end = defined($timeo) ? now + $timeo : undef; +again: + while (1) { + @events = $self->{inot}->read; # Linux::Inotify2::read + last if @events; + return () if defined($timeo) && (!$timeo || (now > $end)); + my $wait = 0.1; + if ($ino_cls) { + vec($rfds = '', $self->{inot}->fileno, 1) = 1; + if (defined $end) { + $wait = $end - now; + $wait = 0 if $wait < 0; + } else { + undef $wait; + } + } + select($rfds, undef, undef, $wait); + } + if ($fh = $self->{watch_fh}) { + sysread($fh, $buf, -s $fh) and + push @ret, split(/^/sm, $buf); + my @st = stat($self->{fn}); + if (!@st || "@st[0, 1]" ne $self->{ino_dev} || + delete_self(@events)) { + delete @$self{qw(ino_dev watch_fh)}; + } + } + if ($fh = $self->{watch_fh} // reopen_file($self)) { + sysread($fh, $buf, -s $fh) and + push @ret, split(/^/sm, $buf); + } + goto again if (!@ret && (!defined($end) || now < $end)); + @ret; +} + +1; |