]> Sergey Matveev's repositories - public-inbox.git/blobdiff - lib/PublicInbox/Import.pm
watch: avoid unnecessary spawning on spam removals
[public-inbox.git] / lib / PublicInbox / Import.pm
index 1a7ed9ce87850852f21444c5fdcb9b2703263684..ee5ca2ea11b4f383e514ecdf117ac1dabe2c14c1 100644 (file)
@@ -12,7 +12,8 @@ use v5.10.1;
 use PublicInbox::Spawn qw(spawn popen_rd);
 use PublicInbox::MID qw(mids mid2path);
 use PublicInbox::Address;
-use PublicInbox::MsgTime qw(msg_timestamp msg_datestamp);
+use PublicInbox::Smsg;
+use PublicInbox::MsgTime qw(msg_datestamp);
 use PublicInbox::ContentHash qw(content_digest);
 use PublicInbox::MDA;
 use PublicInbox::Eml;
@@ -34,7 +35,7 @@ sub new {
                ident => "$name <$email>",
                mark => 1,
                ref => $ref,
-               -inbox => $ibx,
+               ibx => $ibx,
                path_type => '2/38', # or 'v2'
                lock_path => "$git->{git_dir}/ssoma.lock", # v2 changes this
                bytes_added => 0,
@@ -47,34 +48,35 @@ sub gfi_start {
 
        return ($self->{in}, $self->{out}) if $self->{pid};
 
-       my ($out_r, $out_w);
+       my (@ret, $out_r, $out_w);
        pipe($out_r, $out_w) or die "pipe failed: $!";
-       my $git = $self->{git};
 
        $self->lock_acquire;
-
-       local $/ = "\n";
-       my $ref = $self->{ref};
-       chomp($self->{tip} = $git->qx(qw(rev-parse --revs-only), $ref));
-       if ($self->{path_type} ne '2/38' && $self->{tip}) {
-               local $/ = "\0";
-               my @tree = $git->qx(qw(ls-tree -r -z --name-only), $ref);
-               chomp @tree;
-               $self->{-tree} = { map { $_ => 1 } @tree };
+       eval {
+               my ($git, $ref) = @$self{qw(git ref)};
+               local $/ = "\n";
+               chomp($self->{tip} = $git->qx(qw(rev-parse --revs-only), $ref));
+               if ($self->{path_type} ne '2/38' && $self->{tip}) {
+                       local $/ = "\0";
+                       my @t = $git->qx(qw(ls-tree -r -z --name-only), $ref);
+                       chomp @t;
+                       $self->{-tree} = { map { $_ => 1 } @t };
+               }
+               my @cmd = ('git', "--git-dir=$git->{git_dir}",
+                       qw(fast-import --quiet --done --date-format=raw));
+               my ($in_r, $pid) = popen_rd(\@cmd, undef, { 0 => $out_r });
+               $out_w->autoflush(1);
+               $self->{in} = $in_r;
+               $self->{out} = $out_w;
+               $self->{pid} = $pid;
+               $self->{nchg} = 0;
+               @ret = ($in_r, $out_w);
+       };
+       if ($@) {
+               $self->lock_release;
+               die $@;
        }
-
-       my $git_dir = $git->{git_dir};
-       my @cmd = ('git', "--git-dir=$git_dir", qw(fast-import
-                       --quiet --done --date-format=raw));
-       my ($in_r, $pid) = popen_rd(\@cmd, undef, { 0 => $out_r });
-       $out_w->autoflush(1);
-       $self->{in} = $in_r;
-       $self->{out} = $out_w;
-       $self->{pid} = $pid;
-       $self->{nchg} = 0;
-       binmode $out_w, ':raw' or die "binmode :raw failed: $!";
-       binmode $in_r, ':raw' or die "binmode :raw failed: $!";
-       ($in_r, $out_w);
+       @ret;
 }
 
 sub wfail () { die "write to fast-import failed: $!" }
@@ -176,13 +178,16 @@ sub _update_git_info ($$) {
                my $env = { GIT_INDEX_FILE => $index };
                run_die([@cmd, qw(read-tree -m -v -i), $self->{ref}], $env);
        }
-       run_die([@cmd, 'update-server-info']);
-       my $ibx = $self->{-inbox};
-       ($ibx && $self->{path_type} eq '2/38') and eval {
-               require PublicInbox::SearchIdx;
-               my $s = PublicInbox::SearchIdx->new($ibx);
-               $s->index_sync({ ref => $self->{ref} });
-       };
+       eval { run_die([@cmd, 'update-server-info']) };
+       my $ibx = $self->{ibx};
+       if ($ibx && $ibx->version == 1 && -d "$ibx->{inboxdir}/public-inbox" &&
+                               eval { require PublicInbox::SearchIdx }) {
+               eval {
+                       my $s = PublicInbox::SearchIdx->new($ibx);
+                       $s->index_sync({ ref => $self->{ref} });
+               };
+               warn "$ibx->{inboxdir} index failed: $@\n" if $@;
+       }
        eval { run_die([@cmd, qw(gc --auto)]) } if $do_gc;
 }
 
@@ -269,8 +274,8 @@ sub remove {
        (($self->{tip} = ":$commit"), $cur);
 }
 
-sub git_timestamp {
-       my ($ts, $zone) = @_;
+sub git_timestamp ($) {
+       my ($ts, $zone) = @{$_[0]};
        $ts = 0 if $ts < 0; # git uses unsigned times
        "$ts $zone";
 }
@@ -278,14 +283,16 @@ sub git_timestamp {
 sub extract_cmt_info ($;$) {
        my ($mime, $smsg) = @_;
        # $mime is PublicInbox::Eml, but remains Email::MIME-compatible
+       $smsg //= bless {}, 'PublicInbox::Smsg';
+
+       $smsg->populate($mime);
 
        my $sender = '';
-       my $hdr = $mime->header_obj;
-       my $from = $hdr->header('From') // '';
+       my $from = delete($smsg->{From}) // '';
        my ($email) = PublicInbox::Address::emails($from);
        my ($name) = PublicInbox::Address::names($from);
        if (!defined($name) || !defined($email)) {
-               $sender = $hdr->header('Sender') // '';
+               $sender = $mime->header('Sender') // '';
                $name //= (PublicInbox::Address::names($sender))[0];
                $email //= (PublicInbox::Address::emails($sender))[0];
        }
@@ -313,24 +320,18 @@ sub extract_cmt_info ($;$) {
                warn "no name in From: $from or Sender: $sender\n";
        }
 
-       my $subject = $hdr->header('Subject') // '(no subject)';
-       # MIME decoding can create nulls replace them with spaces to protect git
-       $subject =~ tr/\0/ /;
+       my $subject = delete($smsg->{Subject}) // '(no subject)';
        utf8::encode($subject);
-       my $at = git_timestamp(my @at = msg_datestamp($hdr));
-       my $ct = git_timestamp(my @ct = msg_timestamp($hdr));
-       if ($smsg) {
-               $smsg->{ds} = $at[0];
-               $smsg->{ts} = $ct[0];
-       }
-       ($name, $email, $at, $ct, $subject);
+       my $at = git_timestamp(delete $smsg->{-ds});
+       my $ct = git_timestamp(delete $smsg->{-ts});
+       ("$name <$email>", $at, $ct, $subject);
 }
 
 # kill potentially confusing/misleading headers
 sub drop_unwanted_headers ($) {
        my ($mime) = @_;
 
-       $mime->header_set($_) for qw(bytes lines content-length status);
+       $mime->header_set($_) for qw(Bytes Lines Content-Length Status);
        $mime->header_set($_) for @PublicInbox::MDA::BAD_HEADERS;
 }
 
@@ -344,13 +345,12 @@ sub append_mid ($$) {
 }
 
 sub v1_mid0 ($) {
-       my ($mime) = @_;
-       my $hdr = $mime->header_obj;
-       my $mids = mids($hdr);
+       my ($eml) = @_;
+       my $mids = mids($eml);
 
        if (!scalar(@$mids)) { # spam often has no Message-ID
-               my $mid0 = digest2mid(content_digest($mime), $hdr);
-               append_mid($hdr, $mid0);
+               my $mid0 = digest2mid(content_digest($eml), $eml);
+               append_mid($eml, $mid0);
                return $mid0;
        }
        $mids->[0];
@@ -370,7 +370,7 @@ sub clean_tree_v2 ($$$) {
 sub add {
        my ($self, $mime, $check_cb, $smsg) = @_;
 
-       my ($name, $email, $at, $ct, $subject) = extract_cmt_info($mime, $smsg);
+       my ($author, $at, $ct, $subject) = extract_cmt_info($mime, $smsg);
        my $path_type = $self->{path_type};
        my $path;
        if ($path_type eq '2/38') {
@@ -389,7 +389,7 @@ sub add {
 
        # spam check:
        if ($check_cb) {
-               $mime = $check_cb->($mime) or return;
+               $mime = $check_cb->($mime, $self->{ibx}) or return;
        }
 
        my $blob = $self->{mark}++;
@@ -402,7 +402,7 @@ sub add {
        # v2: we need this for Xapian
        if ($smsg) {
                $smsg->{blob} = $self->get_mark(":$blob");
-               $smsg->{bytes} = $n;
+               $smsg->{raw_bytes} = $n;
                $smsg->{-raw_email} = \$raw_email;
        }
        my $ref = $self->{ref};
@@ -414,7 +414,7 @@ sub add {
        }
 
        print $w "commit $ref\nmark :$commit\n",
-               "author $name <$email> $at\n",
+               "author $author $at\n",
                "committer $self->{ident} $ct\n" or wfail;
        print $w "data ", (length($subject) + 1), "\n",
                $subject, "\n\n" or wfail;
@@ -461,20 +461,29 @@ sub init_bare {
        }
 }
 
+# true if locked and active
+sub active { !!$_[0]->{out} }
+
 sub done {
        my ($self) = @_;
        my $w = delete $self->{out} or return;
-       my $r = delete $self->{in} or die 'BUG: missing {in} when done';
-       print $w "done\n" or wfail;
-       my $pid = delete $self->{pid} or die 'BUG: missing {pid} when done';
-       waitpid($pid, 0) == $pid or die 'fast-import did not finish';
-       $? == 0 or die "fast-import failed: $?";
-
-       _update_git_info($self, 1) if delete $self->{nchg};
-
-       $self->lock_release;
-
+       eval {
+               my $r = delete $self->{in} or die 'BUG: missing {in} when done';
+               print $w "done\n" or wfail;
+               my $pid = delete $self->{pid} or
+                               die 'BUG: missing {pid} when done';
+               waitpid($pid, 0) == $pid or die 'fast-import did not finish';
+               $? == 0 or die "fast-import failed: $?";
+       };
+       my $wait_err = $@;
+       my $nchg = delete $self->{nchg};
+       if ($nchg && !$wait_err) {
+               eval { _update_git_info($self, 1) };
+               warn "E: $self->{git}->{git_dir} update info: $@\n" if $@;
+       }
+       $self->lock_release(!!$nchg);
        $self->{git}->cleanup;
+       die $wait_err if $wait_err;
 }
 
 sub atfork_child {
@@ -502,11 +511,11 @@ sub digest2mid ($$) {
 
 sub rewrite_commit ($$$$) {
        my ($self, $oids, $buf, $mime) = @_;
-       my ($name, $email, $at, $ct, $subject);
+       my ($author, $at, $ct, $subject);
        if ($mime) {
-               ($name, $email, $at, $ct, $subject) = extract_cmt_info($mime);
+               ($author, $at, $ct, $subject) = extract_cmt_info($mime);
        } else {
-               $name = $email = '';
+               $author = '<>';
                $subject = 'purged '.join(' ', @$oids);
        }
        @$oids = ();
@@ -515,7 +524,7 @@ sub rewrite_commit ($$$$) {
                my $l = $buf->[$i];
                if ($l =~ /^author .* ([0-9]+ [\+-]?[0-9]+)$/) {
                        $at //= $1;
-                       $buf->[$i] = "author $name <$email> $at\n";
+                       $buf->[$i] = "author $author $at\n";
                } elsif ($l =~ /^committer .* ([0-9]+ [\+-]?[0-9]+)$/) {
                        $ct //= $1;
                        $buf->[$i] = "committer $self->{ident} $ct\n";
@@ -663,8 +672,7 @@ version 1.0
        my $parsed = PublicInbox::Eml->new($message);
        my $ret = $im->add($parsed);
        if (!defined $ret) {
-               warn "duplicate: ",
-                       $parsed->header_obj->header_raw('Message-ID'), "\n";
+               warn "duplicate: ", $parsed->header_raw('Message-ID'), "\n";
        } else {
                print "imported at mark $ret\n";
        }