]> Sergey Matveev's repositories - public-inbox.git/blobdiff - lib/PublicInbox/V2Writable.pm
v2: SDBM-based multi Message-ID queue
[public-inbox.git] / lib / PublicInbox / V2Writable.pm
index 82843241f50a507ad4001723f632b976b30c33c1..1c78ef24f2116ff1e7bc0135df5b0dbe70a84738 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (C) 2018-2019 all contributors <meta@public-inbox.org>
+# Copyright (C) 2018-2020 all contributors <meta@public-inbox.org>
 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
 
 # This interface wraps and mimics PublicInbox::Import
@@ -19,6 +19,8 @@ use PublicInbox::OverIdx;
 use PublicInbox::Msgmap;
 use PublicInbox::Spawn qw(spawn popen_rd);
 use PublicInbox::SearchIdx;
+use PublicInbox::MsgTime qw(msg_timestamp msg_datestamp);
+use PublicInbox::MultiMidQueue;
 use IO::Handle; # ->autoflush
 use File::Temp qw(tempfile);
 
@@ -148,12 +150,14 @@ sub add {
 }
 
 # indexes a message, returns true if checkpointing is needed
-sub do_idx ($$$$$$$) {
-       my ($self, $msgref, $mime, $len, $num, $oid, $mid0) = @_;
-       $self->{over}->add_overview($mime, $len, $num, $oid, $mid0);
-       my $idx = idx_shard($self, $num % $self->{shards});
-       $idx->index_raw($len, $msgref, $num, $oid, $mid0, $mime);
-       my $n = $self->{transact_bytes} += $len;
+sub do_idx ($$$$) {
+       my ($self, $msgref, $mime, $smsg) = @_;
+       $smsg->{ds} //= msg_datestamp($mime->header_obj, $self->{autime});
+       $smsg->{ts} //= msg_timestamp($mime->header_obj, $self->{cotime});
+       $self->{over}->add_overview($mime, $smsg);
+       my $idx = idx_shard($self, $smsg->{num} % $self->{shards});
+       $idx->index_raw($msgref, $mime, $smsg);
+       my $n = $self->{transact_bytes} += $smsg->{bytes};
        $n >= (PublicInbox::SearchIdx::BATCH_BYTES * $self->{shards});
 }
 
@@ -176,12 +180,13 @@ sub _add {
        defined $num or return; # duplicate
        defined $mid0 or die "BUG: $mid0 undefined\n";
        my $im = $self->importer;
-       my $cmt = $im->add($mime);
+       my $smsg = bless { mid => $mid0, num => $num }, 'PublicInbox::Smsg';
+       my $cmt = $im->add($mime, undef, $smsg); # sets $smsg->{ds|ts|blob}
        $cmt = $im->get_mark($cmt);
        $self->{last_commit}->[$self->{epoch_max}] = $cmt;
 
-       my ($oid, $len, $msgref) = @{$im->{last_object}};
-       if (do_idx($self, $msgref, $mime, $len, $num, $oid, $mid0)) {
+       my $msgref = delete $smsg->{-raw_email};
+       if (do_idx($self, $msgref, $mime, $smsg)) {
                $self->checkpoint;
        }
 
@@ -201,11 +206,10 @@ sub v2_num_for {
                # crap, Message-ID is already known, hope somebody just resent:
                foreach my $m (@$mids) {
                        # read-only lookup now safe to do after above barrier
-                       my $existing = lookup_content($self, $mime, $m);
                        # easy, don't store duplicates
                        # note: do not add more diagnostic info here since
                        # it gets noisy on public-inbox-watch restarts
-                       return () if $existing;
+                       return () if content_exists($self, $mime, $m);
                }
 
                # AltId may pre-populate article numbers (e.g. X-Mail-Count
@@ -381,7 +385,7 @@ sub rewrite_internal ($$;$$$) {
        }
        my $over = $self->{over};
        my $cids = content_ids($old_mime);
-       my $removed;
+       my @removed;
        my $mids = mids($old_mime->header_obj);
 
        # We avoid introducing new blobs into git since the raw content
@@ -391,7 +395,7 @@ sub rewrite_internal ($$;$$$) {
        my $mark;
 
        foreach my $mid (@$mids) {
-               my %gone; # num => [ smsg, raw ]
+               my %gone; # num => [ smsg, $mime, raw ]
                my ($id, $prev);
                while (my $smsg = $over->next_by_mid($mid, \$id, \$prev)) {
                        my $msg = get_blob($self, $smsg);
@@ -402,8 +406,7 @@ sub rewrite_internal ($$;$$$) {
                        my $orig = $$msg;
                        my $cur = PublicInbox::MIME->new($msg);
                        if (content_matches($cids, $cur)) {
-                               $smsg->{mime} = $cur;
-                               $gone{$smsg->{num}} = [ $smsg, \$orig ];
+                               $gone{$smsg->{num}} = [ $smsg, $cur, \$orig ];
                        }
                }
                my $n = scalar keys %gone;
@@ -413,15 +416,16 @@ sub rewrite_internal ($$;$$$) {
                                join(',', sort keys %gone), "\n";
                }
                foreach my $num (keys %gone) {
-                       my ($smsg, $orig) = @{$gone{$num}};
-                       # $removed should only be set once assuming
+                       my ($smsg, $mime, $orig) = @{$gone{$num}};
+                       # @removed should only be set once assuming
                        # no bugs in our deduplication code:
-                       $removed = $smsg;
+                       @removed = (undef, $mime, $smsg);
                        my $oid = $smsg->{blob};
                        if ($replace_map) {
                                $replace_map->{$oid} = $sref;
                        } else {
                                ($mark, undef) = $im->remove($orig, $cmt_msg);
+                               $removed[0] = $mark;
                        }
                        $orig = undef;
                        if ($need_reindex) { # ->replace
@@ -441,15 +445,18 @@ sub rewrite_internal ($$;$$$) {
                my $rewrites = _replace_oids($self, $new_mime, $replace_map);
                return { rewrites => $rewrites, need_reindex => $need_reindex };
        }
-       $removed;
+       defined($mark) ? @removed : undef;
 }
 
-# public
+# public (see PublicInbox::Import->remove), but note the 3rd element
+# (retval[2]) is not part of the stable API shared with Import->remove
 sub remove {
        my ($self, $mime, $cmt_msg) = @_;
+       my @ret;
        $self->{-inbox}->with_umask(sub {
-               rewrite_internal($self, $mime, $cmt_msg);
+               @ret = rewrite_internal($self, $mime, $cmt_msg);
        });
+       defined($ret[0]) ? @ret : undef;
 }
 
 sub _replace ($$;$$) {
@@ -549,17 +556,21 @@ W: $list
        }
 
        # make sure we really got the OID:
-       my ($oid, $type, $len) = $self->{-inbox}->git->check($expect_oid);
-       $oid eq $expect_oid or die "BUG: $expect_oid not found after replace";
+       my ($blob, $type, $bytes) = $self->{-inbox}->git->check($expect_oid);
+       $blob eq $expect_oid or die "BUG: $expect_oid not found after replace";
 
        # don't leak FDs to Xapian:
        $self->{-inbox}->git->cleanup;
 
        # reindex modified messages:
        for my $smsg (@$need_reindex) {
-               my $num = $smsg->{num};
-               my $mid0 = $smsg->{mid};
-               do_idx($self, \$raw, $new_mime, $len, $num, $oid, $mid0);
+               my $new_smsg = bless {
+                       blob => $blob,
+                       bytes => $bytes,
+                       num => $smsg->{num},
+                       mid => $smsg->{mid},
+               }, 'PublicInbox::Smsg';
+               do_idx($self, \$raw, $new_mime, $new_smsg);
        }
        $rewritten->{rewrites};
 }
@@ -785,7 +796,6 @@ sub import_init {
        my ($self, $git, $packed_bytes, $tmp) = @_;
        my $im = PublicInbox::Import->new($git, undef, undef, $self->{-inbox});
        $im->{bytes_added} = int($packed_bytes / $PACKING_FACTOR);
-       $im->{want_object_info} = 1;
        $im->{lock_path} = undef;
        $im->{path_type} = 'v2';
        $self->{im} = $im unless $tmp;
@@ -821,7 +831,7 @@ sub get_blob ($$) {
        $ibx->msg_by_smsg($smsg);
 }
 
-sub lookup_content ($$$) {
+sub content_exists ($$$) {
        my ($self, $mime, $mid) = @_;
        my $over = $self->{over};
        my $cids = content_ids($mime);
@@ -833,11 +843,7 @@ sub lookup_content ($$$) {
                        next;
                }
                my $cur = PublicInbox::MIME->new($msg);
-               if (content_matches($cids, $cur)) {
-                       $smsg->{mime} = $cur;
-                       return $smsg;
-               }
-
+               return 1 if content_matches($cids, $cur);
 
                # XXX DEBUG_DIFF is experimental and may be removed
                diff($mid, $cur, $mime) if $ENV{DEBUG_DIFF};
@@ -951,7 +957,13 @@ sub reindex_oid_m ($$$$;$) {
                }
        }
        $sync->{nr}++;
-       if (do_idx($self, $msgref, $mime, $len, $num, $oid, $mid0)) {
+       my $smsg = bless {
+               bytes => $len,
+               num => $num,
+               blob => $oid,
+               mid => $mid0,
+       }, 'PublicInbox::Smsg';
+       if (do_idx($self, $msgref, $mime, $smsg)) {
                reindex_checkpoint($self, $sync, $git);
        }
 }
@@ -980,15 +992,15 @@ sub multi_mid_q_new () {
        $multi_mid
 }
 
-sub multi_mid_q_push ($$) {
-       my ($sync, $oid) = @_;
-       my $multi_mid = $sync->{multi_mid} //= multi_mid_q_new();
+sub multi_mid_q_push ($$$) {
+       my ($self, $sync, $oid) = @_;
+       my $multi_mid = $sync->{multi_mid} //= PublicInbox::MultiMidQueue->new;
        if ($sync->{reindex}) { # no regen on reindex
-               $multi_mid->mid_insert($oid);
+               $multi_mid->push_oid($oid, $self);
        } else {
                my $num = $sync->{regen}--;
                die "BUG: ran out of article numbers" if $num <= 0;
-               $multi_mid->mid_set($num, $oid);
+               $multi_mid->set_oid($num, $oid, $self);
        }
 }
 
@@ -1040,14 +1052,20 @@ sub reindex_oid ($$$$) {
                        # do not delete from {mm_tmp}, since another
                        # single-MID message may use it.
                } else { # handle them at the end:
-                       multi_mid_q_push($sync, $oid);
+                       multi_mid_q_push($self, $sync, $oid);
                }
                return;
        }
        $sync->{mm_tmp}->mid_delete($mid0) or
                die "failed to delete <$mid0> for article #$num\n";
        $sync->{nr}++;
-       if (do_idx($self, $msgref, $mime, $len, $num, $oid, $mid0)) {
+       my $smsg = bless {
+               bytes => $len,
+               num => $num,
+               blob => $oid,
+               mid => $mid0,
+       }, 'PublicInbox::Smsg';
+       if (do_idx($self, $msgref, $mime, $smsg)) {
                reindex_checkpoint($self, $sync, $git);
        }
 }
@@ -1268,15 +1286,17 @@ sub index_epoch ($$$) {
                $pr->("$i.git indexing $range\n");
        }
 
-       my @cmd = qw(log --raw -r --pretty=tformat:%H
+       my @cmd = qw(log --raw -r --pretty=tformat:%H.%at.%ct
                        --no-notes --no-color --no-abbrev --no-renames);
        my $fh = $self->{reindex_pipe} = $git->popen(@cmd, $range);
        my $cmt;
        while (<$fh>) {
                chomp;
                $self->{current_info} = "$i.git $_";
-               if (/\A$x40$/o && !defined($cmt)) {
-                       $cmt = $_;
+               if (/\A($x40)\.([0-9]+)\.([0-9]+)$/o) {
+                       $cmt //= $1;
+                       $self->{autime} = $2;
+                       $self->{cotime} = $3;
                } elsif (/\A:\d{6} 100644 $x40 ($x40) [AM]\tm$/o) {
                        reindex_oid($self, $sync, $git, $1);
                } elsif (/\A:\d{6} 100644 $x40 ($x40) [AM]\td$/o) {
@@ -1333,19 +1353,21 @@ sub index_sync {
        }
        if (my $multi_mid = delete $sync->{multi_mid}) {
                $git //= $self->{-inbox}->git;
-               my ($min, $max) = $multi_mid->minmax;
+               my $min = $multi_mid->{min};
+               my $max = $multi_mid->{max};
                if ($sync->{reindex}) {
                        # we may need to create new Message-IDs if mirrors
                        # were initially indexed with old versions
                        for (my $i = $max; $i >= $min; $i--) {
-                               my $oid = $multi_mid->mid_for($i);
+                               my $oid;
+                               $oid = $multi_mid->get_oid($i, $self) or next;
                                next unless defined $oid;
                                reindex_oid_m($self, $sync, $git, $oid);
                        }
                } else { # regen on initial index
                        for my $num ($min..$max) {
-                               my $oid = $multi_mid->mid_for($num);
-                               next unless defined $oid;
+                               my $oid;
+                               $oid = $multi_mid->get_oid($num, $self) or next;
                                reindex_oid_m($self, $sync, $git, $oid, $num);
                        }
                }