use strict;
use warnings;
use base qw(PublicInbox::Search PublicInbox::Lock);
-use PublicInbox::MIME;
+use PublicInbox::Eml;
use PublicInbox::InboxWritable;
use PublicInbox::MID qw/mid_clean mid_mime mids_for_index/;
use PublicInbox::MsgIter;
use PublicInbox::MsgTime qw(msg_timestamp msg_datestamp);
my $X = \%PublicInbox::Search::X;
my ($DB_CREATE_OR_OPEN, $DB_OPEN);
-use constant {
- BATCH_BYTES => defined($ENV{XAPIAN_FLUSH_THRESHOLD}) ?
- 0x7fffffff : 1_000_000,
- DEBUG => !!$ENV{DEBUG},
-};
+our $BATCH_BYTES = defined($ENV{XAPIAN_FLUSH_THRESHOLD}) ?
+ 0x7fffffff : 1_000_000;
+use constant DEBUG => !!$ENV{DEBUG};
my $xapianlevels = qr/\A(?:full|medium)\z/;
sub need_xapian ($) { $_[0]->{indexlevel} =~ $xapianlevels }
sub _xdb_release {
- my ($self) = @_;
+ my ($self, $wake) = @_;
if (need_xapian($self)) {
my $xdb = delete $self->{xdb} or croak 'not acquired';
$xdb->close;
}
- $self->lock_release if $self->{creat};
+ $self->lock_release($wake) if $self->{creat};
undef;
}
}
}
-sub index_users ($$) {
+sub index_headers ($$) {
my ($self, $smsg) = @_;
-
- my $from = $smsg->from;
- my $to = $smsg->to;
- my $cc = $smsg->cc;
-
- index_text($self, $from, 1, 'A'); # A - author
- index_text($self, $to, 1, 'XTO') if $to ne '';
- index_text($self, $cc, 1, 'XCC') if $cc ne '';
+ my @x = (from => 'A', # Author
+ subject => 'S', to => 'XTO', cc => 'XCC');
+ while (my ($field, $pfx) = splice(@x, 0, 2)) {
+ my $val = $smsg->{$field};
+ index_text($self, $val, 1, $pfx) if $val ne '';
+ }
}
sub index_diff_inc ($$$$) {
if (defined $fn && $fn ne '') {
index_text($self, $fn, 1, 'XFN');
}
+ if ($part->{is_submsg}) {
+ my $mids = mids_for_index($part);
+ index_ids($self, $doc, $part, $mids);
+ my $smsg = bless {}, 'PublicInbox::Smsg';
+ $smsg->populate($part);
+ index_headers($self, $smsg);
+ }
my ($s, undef) = msg_part_text($part, $ct);
defined $s or return;
}
}
+sub index_ids ($$$$) {
+ my ($self, $doc, $hdr, $mids) = @_;
+ for my $mid (@$mids) {
+ index_text($self, $mid, 1, 'XM');
+
+ # because too many Message-IDs are prefixed with
+ # "Pine.LNX."...
+ if ($mid =~ /\w{12,}/) {
+ my @long = ($mid =~ /(\w{3,}+)/g);
+ index_text($self, join(' ', @long), 1, 'XM');
+ }
+ }
+ $doc->add_boolean_term('Q' . $_) for @$mids;
+ for my $l ($hdr->header_raw('List-Id')) {
+ $l =~ /<([^>]+)>/ or next;
+ my $lid = $1;
+ $doc->add_boolean_term('G' . $lid);
+ index_text($self, $lid, 1, 'XL'); # probabilistic
+ }
+}
+
sub add_xapian ($$$$) {
my ($self, $mime, $smsg, $mids) = @_;
- $smsg->{mime} = $mime; # XXX dangerous
my $hdr = $mime->header_obj;
my $doc = $X->{Document}->new;
- my $subj = $smsg->subject;
add_val($doc, PublicInbox::Search::TS(), $smsg->{ts});
my @ds = gmtime($smsg->{ds});
my $yyyymmdd = strftime('%Y%m%d', @ds);
add_val($doc, PublicInbox::Search::YYYYMMDD(), $yyyymmdd);
my $dt = strftime('%Y%m%d%H%M%S', @ds);
add_val($doc, PublicInbox::Search::DT(), $dt);
+ add_val($doc, PublicInbox::Search::BYTES(), $smsg->{bytes});
+ add_val($doc, PublicInbox::Search::UID(), $smsg->{num});
my $tg = term_generator($self);
-
$tg->set_document($doc);
- index_text($self, $subj, 1, 'S') if $subj;
- index_users($self, $smsg);
+ index_headers($self, $smsg);
msg_iter($mime, \&index_xapian, [ $self, $doc ]);
- foreach my $mid (@$mids) {
- index_text($self, $mid, 1, 'XM');
-
- # because too many Message-IDs are prefixed with
- # "Pine.LNX."...
- if ($mid =~ /\w{12,}/) {
- my @long = ($mid =~ /(\w{3,}+)/g);
- index_text($self, join(' ', @long), 1, 'XM');
- }
- }
+ index_ids($self, $doc, $hdr, $mids);
$smsg->{to} = $smsg->{cc} = ''; # WWW doesn't need these, only NNTP
PublicInbox::OverIdx::parse_references($smsg, $hdr, $mids);
my $data = $smsg->to_doc_data;
}
}
}
- $doc->add_boolean_term('Q' . $_) foreach @$mids;
- for my $l ($hdr->header_raw('List-Id')) {
- $l =~ /<([^>]+)>/ or next;
- my $lid = $1;
- $doc->add_boolean_term('G' . $lid);
- index_text($self, $lid, 1, 'XL'); # probabilistic
- }
$self->{xdb}->replace_document($smsg->{num}, $doc);
}
}
sub add_message {
- # mime = Email::MIME object
- my ($self, $mime, $smsg) = @_;
+ # mime = PublicInbox::Eml or Email::MIME object
+ my ($self, $mime, $smsg, $sync) = @_;
my $hdr = $mime->header_obj;
my $mids = mids_for_index($hdr);
$smsg //= bless { blob => '' }, 'PublicInbox::Smsg'; # test-only compat
};
# v1 and tests only:
- $smsg->{ds} //= msg_datestamp($hdr, $self->{autime});
- $smsg->{ts} //= msg_timestamp($hdr, $self->{cotime});
+ $smsg->populate($hdr, $sync);
+ $smsg->{bytes} //= length($mime->as_string);
eval {
# order matters, overview stores every possible piece of
for (; $head != $tail; $head++) {
my $docid = $head->get_docid;
my $doc = $db->get_document($docid);
- my $smsg = PublicInbox::Smsg->wrap($mid);
+ my $smsg = bless { mid => $mid }, 'PublicInbox::Smsg';
$smsg->load_expand($doc);
if ($smsg->{blob} eq $oid) {
push(@delete, $docid);
$self->{mm}->mid_delete(mid_mime($mime));
}
-sub index_both {
- my ($self, $mime, $smsg) = @_;
- my $num = index_mm($self, $mime);
- $smsg->{num} = $num;
- add_message($self, $mime, $smsg);
+# returns the number of bytes to add if given a non-CRLF arg
+sub crlf_adjust ($) {
+ if (index($_[0], "\r\n") < 0) {
+ # common case is LF-only, every \n needs an \r;
+ # so favor a cheap tr// over an expensive m//g
+ $_[0] =~ tr/\n/\n/;
+ } else { # count number of '\n' w/o '\r', expensive:
+ scalar(my @n = ($_[0] =~ m/(?<!\r)\n/g));
+ }
}
-sub unindex_both {
- my ($self, $mime) = @_;
- unindex_blob($self, $mime);
- unindex_mm($self, $mime);
+sub index_both { # git->cat_async callback
+ my ($bref, $oid, $type, $size, $sync) = @_;
+ my ($nr, $max) = @$sync{qw(nr max)};
+ ++$$nr;
+ $$max -= $size;
+ $size += crlf_adjust($$bref);
+ my $smsg = bless { bytes => $size, blob => $oid }, 'PublicInbox::Smsg';
+ my $self = $sync->{sidx};
+ my $eml = PublicInbox::Eml->new($bref);
+ my $num = index_mm($self, $eml);
+ $smsg->{num} = $num;
+ add_message($self, $eml, $smsg, $sync);
}
-sub do_cat_mail {
- my ($git, $blob, $sizeref) = @_;
- my $str = $git->cat_file($blob, $sizeref) or
- die "BUG: $blob not found in $git->{git_dir}";
- PublicInbox::MIME->new($str);
+sub unindex_both { # git->cat_async callback
+ my ($bref, $oid, $type, $size, $self) = @_;
+ my $eml = PublicInbox::Eml->new($bref);
+ unindex_blob($self, $eml);
+ unindex_mm($self, $eml);
}
# called by public-inbox-index
sub index_sync {
my ($self, $opts) = @_;
delete $self->{lock_path} if $opts->{-skip_lock};
- $self->{-inbox}->with_umask(sub { $self->_index_sync($opts) })
-}
-
-sub batch_adjust ($$$$$) {
- my ($max, $bytes, $batch_cb, $latest, $nr) = @_;
- $$max -= $bytes;
- if ($$max <= 0) {
- $$max = BATCH_BYTES;
- $batch_cb->($nr, $latest);
- }
+ $self->{-inbox}->with_umask(\&_index_sync, $self, $opts);
}
sub too_big ($$$) {
# only for v1
sub read_log {
- my ($self, $log, $add_cb, $del_cb, $batch_cb) = @_;
+ my ($self, $log, $batch_cb) = @_;
my $hex = '[a-f0-9]';
my $h40 = $hex .'{40}';
my $addmsg = qr!^:000000 100644 \S+ ($h40) A\t${hex}{2}/${hex}{38}$!;
my $delmsg = qr!^:100644 000000 ($h40) \S+ D\t${hex}{2}/${hex}{38}$!;
my $git = $self->{git};
my $latest;
- my $bytes;
- my $max = BATCH_BYTES;
+ my $max = $BATCH_BYTES;
local $/ = "\n";
my %D;
my $line;
my $newest;
my $nr = 0;
+ my $sync = { sidx => $self, nr => \$nr, max => \$max };
while (defined($line = <$log>)) {
if ($line =~ /$addmsg/o) {
my $blob = $1;
if (delete $D{$blob}) {
+ # make sure pending index writes are done
+ # before writing to ->mm
+ $git->cat_async_wait;
+
if (defined $self->{regen_down}) {
my $num = $self->{regen_down}--;
$self->{mm}->num_highwater($num);
next;
}
next if too_big($self, $git, $blob);
- my $mime = do_cat_mail($git, $blob, \$bytes);
- my $smsg = bless {}, 'PublicInbox::Smsg';
- batch_adjust(\$max, $bytes, $batch_cb, $latest, ++$nr);
- $smsg->{blob} = $blob;
- $smsg->{bytes} = $bytes;
- $add_cb->($self, $mime, $smsg);
+ $git->cat_async($blob, \&index_both, { %$sync });
+ if ($max <= 0) {
+ $git->cat_async_wait;
+ $max = $BATCH_BYTES;
+ $batch_cb->($nr, $latest);
+ }
} elsif ($line =~ /$delmsg/o) {
my $blob = $1;
$D{$blob} = 1 unless too_big($self, $git, $blob);
$latest = $1;
$newest ||= $latest;
} elsif ($line =~ /^author .*? ([0-9]+) [\-\+][0-9]+$/) {
- $self->{autime} = $1;
+ $sync->{autime} = $1;
} elsif ($line =~ /^committer .*? ([0-9]+) [\-\+][0-9]+$/) {
- $self->{cotime} = $1;
+ $sync->{cotime} = $1;
}
}
close($log) or die "git log failed: \$?=$?";
# get the leftovers
foreach my $blob (keys %D) {
- my $mime = do_cat_mail($git, $blob, \$bytes);
- $del_cb->($self, $mime);
+ $git->cat_async($blob, \&unindex_both, $self);
}
+ $git->cat_async_wait;
$batch_cb->($nr, $latest, $newest);
}
} else {
# normal regen is for for fresh data
$self->{regen_down} = $fcount;
+ $self->{regen_down} += $high unless $opts->{reindex};
}
} else {
# Give oldest messages the smallest numbers
my $xdb = $self->begin_txn_lazy;
my $mm = _msgmap_init($self);
do {
- if ($xlog) {
- close($xlog) or die "git log failed: \$?=$?";
- $xlog = undef;
- }
+ $xlog = undef; # stop previous git-log via SIGPIPE
$last_commit = _last_x_commit($self, $mm);
$lx = reindex_from($opts->{reindex}, $last_commit);
} while (_last_x_commit($self, $mm) ne $last_commit);
my $dbh = $mm->{dbh} if $mm;
- my $cb = sub {
+ my $batch_cb = sub {
my ($nr, $commit, $newest) = @_;
if ($dbh) {
if ($newest) {
}
$self->commit_txn_lazy;
$git->cleanup;
- $xdb = _xdb_release($self);
+ $xdb = _xdb_release($self, $nr);
# let another process do some work... <
$pr->("indexed $nr/$self->{ntodo}\n") if $pr && $nr;
if (!$newest) {
};
$dbh->begin_work;
- read_log($self, $xlog, *index_both, *unindex_both, $cb);
+ read_log($self, $xlog, $batch_cb);
}
sub DESTROY {
}
}
-sub begin_txn_lazy {
+sub _begin_txn {
my ($self) = @_;
- return if $self->{txn};
+ my $xdb = $self->{xdb} || $self->_xdb_acquire;
+ $self->{over}->begin_lazy if $self->{over};
+ $xdb->begin_transaction if $xdb;
+ $self->{txn} = 1;
+ $xdb;
+}
- $self->{-inbox}->with_umask(sub {
- my $xdb = $self->{xdb} || $self->_xdb_acquire;
- $self->{over}->begin_lazy if $self->{over};
- $xdb->begin_transaction if $xdb;
- $self->{txn} = 1;
- $xdb;
- });
+sub begin_txn_lazy {
+ my ($self) = @_;
+ $self->{-inbox}->with_umask(\&_begin_txn, $self) if !$self->{txn};
}
# store 'indexlevel=medium' in v2 shard=0 and v1 (only one shard)
}
}
+sub _commit_txn {
+ my ($self) = @_;
+ if (my $xdb = $self->{xdb}) {
+ set_indexlevel($self);
+ $xdb->commit_transaction;
+ }
+ $self->{over}->commit_lazy if $self->{over};
+}
+
sub commit_txn_lazy {
my ($self) = @_;
- delete $self->{txn} or return;
- $self->{-inbox}->with_umask(sub {
- if (my $xdb = $self->{xdb}) {
- set_indexlevel($self);
- $xdb->commit_transaction;
- }
- $self->{over}->commit_lazy if $self->{over};
- });
+ delete($self->{txn}) and
+ $self->{-inbox}->with_umask(\&_commit_txn, $self);
}
sub worker_done {