]> Sergey Matveev's repositories - public-inbox.git/blob - lib/PublicInbox/MailDiff.pm
a0ecef9fbdd75634809b35a58eccd657a4f7ba6b
[public-inbox.git] / lib / PublicInbox / MailDiff.pm
1 # Copyright (C) all contributors <meta@public-inbox.org>
2 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
3 package PublicInbox::MailDiff;
4 use v5.12;
5 use File::Temp 0.19 (); # 0.19 for ->newdir
6 use PublicInbox::ContentHash qw(content_digest);
7 use PublicInbox::MsgIter qw(msg_part_text);
8 use PublicInbox::ViewDiff qw(flush_diff);
9 use PublicInbox::GitAsyncCat;
10
11 sub write_part { # Eml->each_part callback
12         my ($ary, $self) = @_;
13         my ($part, $depth, $idx) = @$ary;
14         if ($idx ne '1' || $self->{-raw_hdr}) {
15                 open my $fh, '>', "$self->{curdir}/$idx.hdr" or die "open: $!";
16                 print $fh ${$part->{hdr}} or die "print $!";
17                 close $fh or die "close $!";
18         }
19         my $ct = $part->content_type || 'text/plain';
20         my ($s, $err) = msg_part_text($part, $ct);
21         my $sfx = defined($s) ? 'txt' : 'bin';
22         open my $fh, '>', "$self->{curdir}/$idx.$sfx" or die "open: $!";
23         print $fh ($s // $part->body) or die "print $!";
24         close $fh or die "close $!";
25 }
26
27 # public
28 sub dump_eml ($$$) {
29         my ($self, $dir, $eml) = @_;
30         local $self->{curdir} = $dir;
31         mkdir $dir or die "mkdir($dir): $!";
32         $eml->each_part(\&write_part, $self);
33
34         return if $self->{ctx}; # don't need content_digest noise in WWW UI
35         require PublicInbox::ContentDigestDbg;
36
37         # XXX is this even useful?  perhaps hide it behind a CLI switch
38         open my $fh, '>', "$dir/content_digest" or die "open: $!";
39         my $dig = PublicInbox::ContentDigestDbg->new($fh);
40         content_digest($eml, $dig);
41         print $fh "\n", $dig->hexdigest, "\n" or die "print $!";
42         close $fh or die "close: $!";
43 }
44
45 # public
46 sub prep_a ($$) {
47         my ($self, $eml) = @_;
48         $self->{tmp} = File::Temp->newdir('mail-diff-XXXX', TMPDIR => 1);
49         dump_eml($self, "$self->{tmp}/a", $eml);
50 }
51
52 sub next_smsg ($) {
53         my ($self) = @_;
54         my $ctx = $self->{ctx};
55         my $over = $ctx->{ibx}->over;
56         $self->{smsg} = $over ? $over->next_by_mid(@{$self->{next_arg}})
57                         : $ctx->gone('over');
58         if (!$self->{smsg}) {
59                 $ctx->write($ctx->_html_end);
60                 return $ctx->close;
61         }
62         my $async = $self->{ctx}->{env}->{'pi-httpd.async'};
63         $async->(undef, undef, $self) if $async # PublicInbox::HTTPD::Async->new
64 }
65
66 sub emit_msg_diff {
67         my ($bref, $self) = @_; # bref is `git diff' output
68         # will be escaped to `&#8226;' in HTML
69         $self->{ctx}->{ibx}->{obfuscate} and
70                 obfuscate_addrs($self->{ctx}->{ibx}, $$bref, "\x{2022}");
71         $$bref =~ s/\r+\n/\n/sg;
72         print { $self->{ctx}->{zfh} } '</pre><hr><pre>' if $self->{nr} > 1;
73         flush_diff($self->{ctx}, $bref);
74         next_smsg($self);
75 }
76
77 sub do_diff {
78         my ($self, $eml) = @_;
79         my $n = 'N'.(++$self->{nr});
80         my $dir = "$self->{tmp}/$n";
81         $self->dump_eml($dir, $eml);
82         my $cmd = [ qw(git diff --no-index --no-color -- a), $n ];
83         my $opt = { -C => "$self->{tmp}", quiet => 1 };
84         my $qsp = PublicInbox::Qspawn->new($cmd, undef, $opt);
85         $qsp->psgi_qx($self->{ctx}->{env}, undef, \&emit_msg_diff, $self);
86 }
87
88 sub diff_msg_i {
89         my ($self, $eml) = @_;
90         if ($eml) {
91                 if ($self->{tmp}) { # 2nd..last message
92                         do_diff($self, $eml);
93                 } else { # first message:
94                         prep_a($self, $eml);
95                         next_smsg($self);
96                 }
97         } else {
98                 warn "W: $self->{smsg}->{blob} missing\n";
99                 next_smsg($self);
100         }
101 }
102
103 sub diff_msg_i_async {
104         my ($bref, $oid, $type, $size, $self) = @_;
105         diff_msg_i($self, $bref ? PublicInbox::Eml->new($bref) : undef);
106 }
107
108 sub event_step {
109         my ($self) = @_;
110         eval {
111                 my $ctx = $self->{ctx};
112                 if ($ctx->{env}->{'pi-httpd.async'}) {
113                         ibx_async_cat($ctx->{ibx}, $self->{smsg}->{blob},
114                                         \&diff_msg_i_async, $self);
115                 } else {
116                         diff_msg_i($self, $ctx->{ibx}->smsg_eml($self->{smsg}));
117                 }
118         };
119         if ($@) {
120                 warn "E: $@";
121                 delete $self->{smsg};
122                 $self->{ctx}->close;
123         }
124 }
125
126 sub begin_mail_diff {
127         my ($self) = @_;
128         if (my $async = $self->{ctx}->{env}->{'pi-httpd.async'}) {
129                 $async->(undef, undef, $self); # PublicInbox::HTTPD::Async->new
130         } else {
131                 event_step($self) while $self->{smsg};
132         }
133 }
134
135 1;