]> Sergey Matveev's repositories - public-inbox.git/blob - lib/PublicInbox/LeiDedupe.pm
5c83fd8007ce55438f989ebd3a2bc835d05b7373
[public-inbox.git] / lib / PublicInbox / LeiDedupe.pm
1 # Copyright (C) 2020-2021 all contributors <meta@public-inbox.org>
2 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
3 package PublicInbox::LeiDedupe;
4 use strict;
5 use v5.10.1;
6 use PublicInbox::SharedKV;
7 use PublicInbox::ContentHash qw(content_hash);
8 use Digest::SHA ();
9
10 # n.b. mutt sets most of these headers not sure about Bytes
11 our @OID_IGNORE = qw(Status X-Status Content-Length Lines Bytes);
12
13 # best-effort regeneration of OID when augmenting existing results
14 sub _regen_oid ($) {
15         my ($eml) = @_;
16         my @stash; # stash away headers we shouldn't have in git
17         for my $k (@OID_IGNORE) {
18                 my @v = $eml->header_raw($k) or next;
19                 push @stash, [ $k, \@v ];
20                 $eml->header_set($k); # restore below
21         }
22         my $dig = Digest::SHA->new(1); # XXX SHA256 later
23         my $buf = $eml->as_string;
24         $dig->add('blob '.length($buf)."\0");
25         $dig->add($buf);
26         undef $buf;
27
28         for my $kv (@stash) { # restore stashed headers
29                 my ($k, @v) = @$kv;
30                 $eml->header_set($k, @v);
31         }
32         $dig->digest;
33 }
34
35 sub _oidbin ($) { defined($_[0]) ? pack('H*', $_[0]) : undef }
36
37 sub smsg_hash ($) {
38         my ($smsg) = @_;
39         my $dig = Digest::SHA->new(256);
40         my $x = join("\0", @$smsg{qw(from to cc ds subject references mid)});
41         utf8::encode($x);
42         $dig->add($x);
43         $dig->digest;
44 }
45
46 # the paranoid option
47 sub dedupe_oid ($) {
48         my ($skv) = @_;
49         (sub { # may be called in a child process
50                 my ($eml, $oid) = @_;
51                 $skv->set_maybe(_oidbin($oid) // _regen_oid($eml), '');
52         }, sub {
53                 my ($smsg) = @_;
54                 $skv->set_maybe(_oidbin($smsg->{blob}), '');
55         });
56 }
57
58 # dangerous if there's duplicate messages with different Message-IDs
59 sub dedupe_mid ($) {
60         my ($skv) = @_;
61         (sub { # may be called in a child process
62                 my ($eml, $oid) = @_;
63                 # TODO: lei will support non-public messages w/o Message-ID
64                 my $mid = $eml->header_raw('Message-ID') // _oidbin($oid) //
65                         content_hash($eml);
66                 $skv->set_maybe($mid, '');
67         }, sub {
68                 my ($smsg) = @_;
69                 my $mid = $smsg->{mid};
70                 $mid = undef if $mid eq '';
71                 $mid //= smsg_hash($smsg) // _oidbin($smsg->{blob});
72                 $skv->set_maybe($mid, '');
73         });
74 }
75
76 # our default deduplication strategy (used by v2, also)
77 sub dedupe_content ($) {
78         my ($skv) = @_;
79         (sub { # may be called in a child process
80                 my ($eml) = @_; # oid = $_[1], ignored
81                 $skv->set_maybe(content_hash($eml), '');
82         }, sub {
83                 my ($smsg) = @_;
84                 $skv->set_maybe(smsg_hash($smsg), '');
85         });
86 }
87
88 # no deduplication at all
89 sub true { 1 }
90 sub dedupe_none ($) { (\&true, \&true) }
91
92 sub new {
93         my ($cls, $lei) = @_;
94         my $dd = $lei->{opt}->{dedupe} // 'content';
95         my $dst = $lei->{ovv}->{dst};
96
97         # allow "none" to bypass Eml->new if writing to directory:
98         return if ($dd eq 'none' && substr($dst // '', -1) eq '/');
99         my $m = "dedupe_$dd";
100         $cls->can($m) or die "unsupported dedupe strategy: $dd\n";
101         my $skv = $dd eq 'none' ? undef : PublicInbox::SharedKV->new;
102
103         # [ $skv, $eml_cb, $smsg_cb, "dedupe_$dd" ]
104         bless [ $skv, undef, undef, $m ], $cls;
105 }
106
107 # returns true on seen messages according to the deduplication strategy,
108 # returns false if unseen
109 sub is_dup {
110         my ($self, $eml, $oid) = @_;
111         !$self->[1]->($eml, $oid);
112 }
113
114 sub is_smsg_dup {
115         my ($self, $smsg) = @_;
116         !$self->[2]->($smsg);
117 }
118
119 sub prepare_dedupe {
120         my ($self) = @_;
121         my $skv = $self->[0];
122         $self->[1] or @$self[1,2] = $self->can($self->[3])->($skv);
123         $skv ? $skv->dbh : undef;
124 }
125
126 sub pause_dedupe {
127         my ($self) = @_;
128         my $skv = $self->[0];
129         $skv->dbh_release;
130         delete($skv->{dbh}) if $skv;
131 }
132
133 1;