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