]> Sergey Matveev's repositories - public-inbox.git/blob - lib/PublicInbox/LeiDedupe.pm
imap+nntp: share COMPRESS implementation
[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::ContentHash qw(content_hash git_sha);
7 use Digest::SHA ();
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 = git_sha(1, $eml);
22         for my $kv (@stash) { # restore stashed headers
23                 my ($k, @v) = @$kv;
24                 $eml->header_set($k, @v);
25         }
26         $dig->digest;
27 }
28
29 sub _oidbin ($) { defined($_[0]) ? pack('H*', $_[0]) : undef }
30
31 sub smsg_hash ($) {
32         my ($smsg) = @_;
33         my $dig = Digest::SHA->new(256);
34         my $x = join("\0", @$smsg{qw(from to cc ds subject references mid)});
35         utf8::encode($x);
36         $dig->add($x);
37         $dig->digest;
38 }
39
40 # the paranoid option
41 sub dedupe_oid ($) {
42         my ($skv) = @_;
43         (sub { # may be called in a child process
44                 my ($eml, $oidhex) = @_;
45                 $skv->set_maybe(_oidbin($oidhex) // _regen_oid($eml), '');
46         }, sub {
47                 my ($smsg) = @_;
48                 $skv->set_maybe(_oidbin($smsg->{blob}), '');
49         });
50 }
51
52 # dangerous if there's duplicate messages with different Message-IDs
53 sub dedupe_mid ($) {
54         my ($skv) = @_;
55         (sub { # may be called in a child process
56                 my ($eml, $oidhex) = @_;
57                 # lei supports non-public drafts w/o Message-ID
58                 my $mid = $eml->header_raw('Message-ID') // _oidbin($oidhex) //
59                         content_hash($eml);
60                 $skv->set_maybe($mid, '');
61         }, sub {
62                 my ($smsg) = @_;
63                 my $mid = $smsg->{mid};
64                 $mid = undef if $mid eq '';
65                 $mid //= smsg_hash($smsg) // _oidbin($smsg->{blob});
66                 $skv->set_maybe($mid, '');
67         });
68 }
69
70 # our default deduplication strategy (used by v2, also)
71 sub dedupe_content ($) {
72         my ($skv) = @_;
73         (sub { # may be called in a child process
74                 my ($eml) = @_; # $oidhex = $_[1], ignored
75                 $skv->set_maybe(content_hash($eml), '');
76         }, sub {
77                 my ($smsg) = @_;
78                 $skv->set_maybe(smsg_hash($smsg), '');
79         });
80 }
81
82 # no deduplication at all
83 sub true { 1 }
84 sub dedupe_none ($) { (\&true, \&true) }
85
86 sub new {
87         my ($cls, $lei) = @_;
88         my $dd = $lei->{opt}->{dedupe} // 'content';
89         my $dst = $lei->{ovv}->{dst};
90
91         # allow "none" to bypass Eml->new if writing to directory:
92         return if ($dd eq 'none' && substr($dst // '', -1) eq '/');
93         my $m = "dedupe_$dd";
94         $cls->can($m) or die "unsupported dedupe strategy: $dd\n";
95         my $skv;
96         if ($dd ne 'none') {
97                 require PublicInbox::SharedKV;
98                 $skv = PublicInbox::SharedKV->new;
99         }
100         # [ $skv, $eml_cb, $smsg_cb, "dedupe_$dd" ]
101         bless [ $skv, undef, undef, $m ], $cls;
102 }
103
104 # returns true on seen messages according to the deduplication strategy,
105 # returns false if unseen
106 sub is_dup {
107         my ($self, $eml, $smsg) = @_;
108         !$self->[1]->($eml, $smsg ? $smsg->{blob} : undef);
109 }
110
111 sub is_smsg_dup {
112         my ($self, $smsg) = @_;
113         !$self->[2]->($smsg);
114 }
115
116 sub prepare_dedupe {
117         my ($self) = @_;
118         my $skv = $self->[0];
119         $self->[1] or @$self[1,2] = $self->can($self->[3])->($skv);
120         $skv ? $skv->dbh : undef;
121 }
122
123 sub pause_dedupe {
124         my ($self) = @_;
125         my $skv = $self->[0] or return;
126         $skv->dbh_release;
127         delete($skv->{dbh}) if $skv;
128 }
129
130 sub has_entries {
131         my $skv = $_[0]->[0] or return undef;
132         $skv->has_entries;
133 }
134
135 1;