lib/PublicInbox/Hval.pm | 2 +- lib/PublicInbox/MailDiff.pm | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/PublicInbox/View.pm | 29 ++++++++++++++++++++++++++++- lib/PublicInbox/WWW.pm | 6 +++++- t/psgi_v2.t | 3 +++ diff --git a/lib/PublicInbox/Hval.pm b/lib/PublicInbox/Hval.pm index 00b3c8b45946abff63461132bac7075307253b5b..0677865e3958f3bd02dd54f80955e26a259d28d0 100644 --- a/lib/PublicInbox/Hval.pm +++ b/lib/PublicInbox/Hval.pm @@ -118,7 +118,7 @@ sub to_attr ($) { my ($str) = @_; - # git would never do this to us: + # git would never do this to us, mail diff uses // to prevent anchors: return if index($str, '//') >= 0; my $first = ''; diff --git a/lib/PublicInbox/MailDiff.pm b/lib/PublicInbox/MailDiff.pm index 06eb3a0dd4801f77652de03c1cb9f8475a965c51..0ed06f9a25269350a30c268deaaa2ba67fe79d06 100644 --- a/lib/PublicInbox/MailDiff.pm +++ b/lib/PublicInbox/MailDiff.pm @@ -7,6 +7,8 @@ use PublicInbox::ContentHash qw(content_digest); use PublicInbox::ContentDigestDbg; use Data::Dumper (); use PublicInbox::MsgIter qw(msg_part_text); +use PublicInbox::ViewDiff qw(flush_diff); +use PublicInbox::GitAsyncCat; sub write_part { # Eml->each_part callback my ($ary, $self) = @_; @@ -31,6 +33,9 @@ local $self->{curdir} = $dir; mkdir $dir or die "mkdir($dir): $!"; $eml->each_part(\&write_part, $self); + return if $self->{ctx}; # don't need content_digest noise in WWW UI + + # XXX is this even useful? perhaps hide it behind a CLI switch open my $fh, '>', "$dir/content_digest" or die "open: $!"; my $dig = PublicInbox::ContentDigestDbg->new($fh); local $Data::Dumper::Useqq = 1; @@ -45,6 +50,89 @@ sub prep_a ($$) { my ($self, $eml) = @_; $self->{tmp} = File::Temp->newdir('mail-diff-XXXX', TMPDIR => 1); dump_eml($self, "$self->{tmp}/a", $eml); +} + +sub next_smsg ($) { + my ($self) = @_; + my $ctx = $self->{ctx}; + my $over = $ctx->{ibx}->over; + $self->{smsg} = $over ? $over->next_by_mid(@{$self->{next_arg}}) + : $ctx->gone('over'); + if (!$self->{smsg}) { + $ctx->write($ctx->_html_end); + return $ctx->close; + } + my $async = $self->{ctx}->{env}->{'pi-httpd.async'}; + $async->(undef, undef, $self) if $async # PublicInbox::HTTPD::Async->new +} + +sub emit_msg_diff { + my ($bref, $self) = @_; # bref is `git diff' output + # will be escaped to `•' in HTML + $self->{ctx}->{ibx}->{obfuscate} and + obfuscate_addrs($self->{ctx}->{ibx}, $$bref, "\x{2022}"); + $$bref =~ s/\r+\n/\n/sg; + print { $self->{ctx}->{zfh} } '
' if $self->{nr} > 1;
+ flush_diff($self->{ctx}, $bref);
+ next_smsg($self);
+}
+
+sub do_diff {
+ my ($self, $eml) = @_;
+ my $n = 'N'.(++$self->{nr});
+ my $dir = "$self->{tmp}/$n";
+ $self->dump_eml($dir, $eml);
+ my $cmd = [ qw(git diff --no-index --no-color -- a), $n ];
+ my $opt = { -C => "$self->{tmp}", quiet => 1 };
+ my $qsp = PublicInbox::Qspawn->new($cmd, undef, $opt);
+ $qsp->psgi_qx($self->{ctx}->{env}, undef, \&emit_msg_diff, $self);
+}
+
+sub diff_msg_i {
+ my ($self, $eml) = @_;
+ if ($eml) {
+ if ($self->{tmp}) { # 2nd..last message
+ do_diff($self, $eml);
+ } else { # first message:
+ prep_a($self, $eml);
+ next_smsg($self);
+ }
+ } else {
+ warn "W: $self->{smsg}->{blob} missing\n";
+ next_smsg($self);
+ }
+}
+
+sub diff_msg_i_async {
+ my ($bref, $oid, $type, $size, $self) = @_;
+ diff_msg_i($self, $bref ? PublicInbox::Eml->new($bref) : undef);
+}
+
+sub event_step {
+ my ($self) = @_;
+ eval {
+ my $ctx = $self->{ctx};
+ if ($ctx->{env}->{'pi-httpd.async'}) {
+ ibx_async_cat($ctx->{ibx}, $self->{smsg}->{blob},
+ \&diff_msg_i_async, $self);
+ } else {
+ diff_msg_i($self, $ctx->{ibx}->smsg_eml($self->{smsg}));
+ }
+ };
+ if ($@) {
+ warn "E: $@";
+ delete $self->{smsg};
+ $self->{ctx}->close;
+ }
+}
+
+sub begin_mail_diff {
+ my ($self) = @_;
+ if (my $async = $self->{ctx}->{env}->{'pi-httpd.async'}) {
+ $async->(undef, undef, $self); # PublicInbox::HTTPD::Async->new
+ } else {
+ event_step($self) while $self->{smsg};
+ }
}
1;
diff --git a/lib/PublicInbox/View.pm b/lib/PublicInbox/View.pm
index 071a20930827654fc0af9c0b0f1108d4bb5a4aa4..b8d6d85e77470737d4200718c4771d540dd08618 100644
--- a/lib/PublicInbox/View.pm
+++ b/lib/PublicInbox/View.pm
@@ -623,7 +623,8 @@ warn "W: BUG? @$mids not deduplicated properly\n";
return;
}
$ctx->{-html_tip} =
-"WARNING: multiple messages have this Message-ID\n
";
+qq[WARNING: multiple messages have this Message-ID (diff)
];
} else {
$ctx->{first_hdr} = $eml->header_obj;
$ctx->{chash} = content_hash($eml) if $ctx->{smsg}; # reused MID
@@ -1223,6 +1224,32 @@ my ($ctx, $level, $node) = @_;
my ($beg, $end) = thread_adj_level($ctx, $level);
$beg . ''. ghost_parent($ctx->{-upfx}, $node->{mid} // '?')
. '' . $end;
+}
+
+# /$INBOX/$MSGID/d/ endpoint
+sub diff_msg {
+ my ($ctx) = @_;
+ require PublicInbox::MailDiff;
+ my $ibx = $ctx->{ibx};
+ my $over = $ibx->over or return no_over_html($ctx);
+ my ($id, $prev);
+ my $md = bless { ctx => $ctx }, 'PublicInbox::MailDiff';
+ my $next_arg = $md->{next_arg} = [ $ctx->{mid}, \$id, \$prev ];
+ my $smsg = $md->{smsg} = $over->next_by_mid(@$next_arg) or
+ return; # undef == 404
+ $ctx->{-t_max} = $smsg->{ts};
+ $ctx->{-upfx} = '../../';
+ $ctx->{-apfx} = '//'; # fail on to_attr()
+ $ctx->{-linkify} = PublicInbox::Linkify->new;
+ my $mid = ascii_html($smsg->{mid});
+ $ctx->{-title_html} = "diff for duplicates of <$mid>";
+ PublicInbox::WwwStream::html_init($ctx);
+ print { $ctx->{zfh} } 'diff for duplicates of <',
+ $mid, ">\n\n";
+ sub {
+ $ctx->attach($_[0]->([200, delete $ctx->{-res_hdr}]));
+ $md->begin_mail_diff;
+ };
}
1;
diff --git a/lib/PublicInbox/WWW.pm b/lib/PublicInbox/WWW.pm
index f861b192a7ee7693d88c73a311e6683dfea0c2b0..9ffcb879d9ef5be1c17f199a351bb5906ab5bedc 100644
--- a/lib/PublicInbox/WWW.pm
+++ b/lib/PublicInbox/WWW.pm
@@ -25,7 +25,7 @@
# TODO: consider a routing tree now that we have more endpoints:
our $INBOX_RE = qr!\A/([\w\-][\w\.\-\+]*)!;
our $MID_RE = qr!([^/]+)!;
-our $END_RE = qr!(T/|t/|t\.mbox(?:\.gz)?|t\.atom|raw|)!;
+our $END_RE = qr!(T/|t/|d/|t\.mbox(?:\.gz)?|t\.atom|raw|)!;
our $ATTACH_RE = qr!([0-9][0-9\.]*)-($PublicInbox::Hval::FN)!;
our $OID_RE = qr![a-f0-9]{7,}!;
@@ -452,6 +452,10 @@ 'raw' eq $e and return get_mid_txt($ctx);
# legacy, but no redirect for compatibility:
'f/' eq $e and return get_mid_html($ctx);
+ if ($e eq 'd/') {
+ require PublicInbox::View;
+ return PublicInbox::View::diff_msg($ctx);
+ }
r404($ctx);
}
diff --git a/t/psgi_v2.t b/t/psgi_v2.t
index 6b1b3a39bd7f29f5656482aae6ed30dc9cb8b592..f709c3c7a04ba8ef06eb20b6df4e8d0ce50bc98e 100644
--- a/t/psgi_v2.t
+++ b/t/psgi_v2.t
@@ -220,6 +220,9 @@ foreach my $mid ('a-mid@b', $new_mid, $third) {
like($raw, qr!>\Q$mid\E!s, "Message-ID $mid shown");
}
like($raw, qr/\b3\+ messages\b/, 'thread overview shown');
+
+ $res = $cb->(GET('/v2test/a-mid@b/d/'));
+ is($res->code, 200, '/d/ (diff) endpoint works');
};
test_psgi(sub { $www->call(@_) }, $client1);