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