public-inbox.git  about / heads / tags
an "archives first" approach to mailing lists
blob 125360fee6be6d5eb7d322efe4e2aea95e77213d 3666 bytes (raw)
$ git show HEAD:lib/PublicInbox/MailDiff.pm	# shows this blob on the CLI

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
 
# Copyright (C) all contributors <meta@public-inbox.org>
# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
package PublicInbox::MailDiff;
use v5.12;
use File::Temp 0.19 (); # 0.19 for ->newdir
use PublicInbox::ContentHash qw(content_digest);
use PublicInbox::MsgIter qw(msg_part_text);
use PublicInbox::ViewDiff qw(flush_diff);
use PublicInbox::GitAsyncCat;
use PublicInbox::ContentDigestDbg;
use PublicInbox::Qspawn;
use PublicInbox::IO qw(write_file);
use autodie qw(close mkdir);

sub write_part { # Eml->each_part callback
	my ($ary, $self) = @_;
	my ($part, $depth, $idx) = @$ary;
	if ($idx ne '1' || $self->{-raw_hdr}) { # lei mail-diff --raw-header
		write_file '>', "$self->{curdir}/$idx.hdr", ${$part->{hdr}};
	}
	my $ct = $part->content_type || 'text/plain';
	my ($s, $err) = msg_part_text($part, $ct);
	my $sfx = defined($s) ? 'txt' : 'bin';
	$s //= $part->body;
	$s =~ s/\r\n/\n/gs; # TODO: consider \r+\n to match View
	$s =~ s/\s*\z//s;
	write_file '>:utf8', "$self->{curdir}/$idx.$sfx", $s, "\n";
}

# public
sub dump_eml ($$$) {
	my ($self, $dir, $eml) = @_;
	local $self->{curdir} = $dir;
	mkdir $dir;
	$eml->each_part(\&write_part, $self);
	my $fh = write_file '>', "$dir/content_digest";
	my $dig = PublicInbox::ContentDigestDbg->new($fh);
	content_digest($eml, $dig);
	say $fh "\n", $dig->hexdigest;
	close $fh;
}

# public
sub prep_a ($$) {
	my ($self, $eml) = @_;
	$self->{tmp} = File::Temp->newdir('mail-diff-XXXX', TMPDIR => 1);
	dump_eml($self, "$self->{tmp}/a", $eml);
}

# WWW-specific stuff below (TODO: split out for non-lei)

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('</pre>', $ctx->_html_end);
		return $ctx->close;
	}
	PublicInbox::DS::requeue($self) if $ctx->{env}->{'pi-httpd.async'};
}

sub emit_msg_diff {
	my ($bref, $self) = @_; # bref is `git diff' output
	require PublicInbox::Hval;
	PublicInbox::Hval::utf8_maybe($$bref);

	# will be escaped to `&#8226;' in HTML
	$self->{ctx}->{ibx}->{obfuscate} and
		PublicInbox::Hval::obfuscate_addrs($self->{ctx}->{ibx},
						$$bref, "\x{2022}");
	print { $self->{ctx}->{zfh} } '</pre><hr><pre>' 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 ($self->{ctx}->{env}->{'pi-httpd.async'}) {
		PublicInbox::DS::requeue($self);
	} else {
		event_step($self) while $self->{smsg};
	}
}

1;

git clone https://public-inbox.org/public-inbox.git
git clone http://7fh6tueqddpjyxjmgtdiueylzoqt6pt7hec3pukyptlmohoowvhde4yd.onion/public-inbox.git