public-inbox.git  about / heads / tags
an "archives first" approach to mailing lists
blob 84340a3518f4a0f0b587167db295e99a037c37e4 2694 bytes (raw)
$ git show HEAD:lib/PublicInbox/TailNotify.pm	# shows this blob on the CLI

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
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;

git clone https://public-inbox.org/public-inbox.git
git clone http://7fh6tueqddpjyxjmgtdiueylzoqt6pt7hec3pukyptlmohoowvhde4yd.onion/public-inbox.git