# Copyright (C) all contributors # License: AGPL-3.0+ # 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;