]> Sergey Matveev's repositories - public-inbox.git/blob - lib/PublicInbox/LeiImport.pm
lei import: speed up repeated Maildir imports
[public-inbox.git] / lib / PublicInbox / LeiImport.pm
1 # Copyright (C) 2021 all contributors <meta@public-inbox.org>
2 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
3
4 # front-end for the "lei import" sub-command
5 package PublicInbox::LeiImport;
6 use strict;
7 use v5.10.1;
8 use parent qw(PublicInbox::IPC PublicInbox::LeiInput);
9 use PublicInbox::InboxWritable qw(eml_from_path);
10
11 # /^input_/ subs are used by (or override) PublicInbox::LeiInput superclass
12
13 sub input_eml_cb { # used by PublicInbox::LeiInput::input_fh
14         my ($self, $eml, $vmd) = @_;
15         my $xoids = $self->{lei}->{ale}->xoids_for($eml);
16         if (my $all_vmd = $self->{all_vmd}) {
17                 @$vmd{keys %$all_vmd} = values %$all_vmd;
18         }
19         $self->{lei}->{sto}->ipc_do('set_eml', $eml, $vmd, $xoids);
20 }
21
22 sub input_mbox_cb { # MboxReader callback
23         my ($eml, $self) = @_;
24         my $vmd;
25         if ($self->{-import_kw}) {
26                 my $kw = PublicInbox::MboxReader::mbox_keywords($eml);
27                 $vmd = { kw => $kw } if scalar(@$kw);
28         }
29         input_eml_cb($self, $eml, $vmd);
30 }
31
32 sub pmdir_cb { # called via wq_io_do from LeiPmdir->each_mdir_fn
33         my ($self, $f, @args) = @_;
34         my ($folder, $bn) = ($f =~ m!\A(.+?)/(?:new|cur)/([^/]+)\z!) or
35                 die "BUG: $f was not from a Maildir?\n";
36         my $fl = PublicInbox::MdirReader::maildir_basename_flags($bn);
37         return if index($fl, 'T') >= 0; # no Trashed messages
38         my $kw = PublicInbox::MdirReader::flags2kw($fl);
39         substr($folder, 0, 0) = 'maildir:'; # add prefix
40         my $lms = $self->{-lms_ro};
41         my $oidbin = $lms ? $lms->name_oidbin($folder, $bn) : undef;
42         my @docids = defined($oidbin) ?
43                         $self->{over}->oidbin_exists($oidbin) : ();
44         my $vmd = $self->{-import_kw} ? { kw => $kw } : undef;
45         if (scalar @docids) {
46                 $self->{lse}->kw_changed(undef, $kw, \@docids) or return;
47         }
48         if (my $eml = eml_from_path($f)) {
49                 $vmd->{sync_info} = [ $folder, \$bn ] if $self->{-mail_sync};
50                 $self->input_eml_cb($eml, $vmd);
51         }
52 }
53
54 sub input_net_cb { # imap_each / nntp_each
55         my ($uri, $uid, $kw, $eml, $self) = @_;
56         if (defined $eml) {
57                 my $vmd = $self->{-import_kw} ? { kw => $kw } : undef;
58                 $vmd->{sync_info} = [ $$uri, $uid ] if $self->{-mail_sync};
59                 $self->input_eml_cb($eml, $vmd);
60         } elsif (my $ikw = $self->{lei}->{ikw}) { # old message, kw only
61                 # we send $uri as a bare SCALAR and not a URIimap ref to
62                 # reduce socket traffic:
63                 $ikw->wq_io_do('ck_update_kw', [], $$uri, $uid, $kw);
64         }
65 }
66
67 sub do_import_index ($$@) {
68         my ($self, $lei, @inputs) = @_;
69         my $sto = $lei->_lei_store(1);
70         $sto->write_prepare($lei);
71         $self->{-import_kw} = $lei->{opt}->{kw} // 1;
72         my $vmd_mod = $self->vmd_mod_extract(\@inputs);
73         return $lei->fail(join("\n", @{$vmd_mod->{err}})) if $vmd_mod->{err};
74         $self->{all_vmd} = $vmd_mod if scalar keys %$vmd_mod;
75         $lei->ale; # initialize for workers to read (before LeiPmdir->new)
76         $self->{-mail_sync} = $lei->{opt}->{'mail-sync'} // 1;
77         $self->prepare_inputs($lei, \@inputs) or return;
78
79         my $j = $lei->{opt}->{jobs} // 0;
80         $j =~ /\A([0-9]+),[0-9]+\z/ and $j = $1 + 0;
81         $j ||= scalar(@{$self->{inputs}}) || 1;
82         my $ikw;
83         if (my $net = $lei->{net}) {
84                 # $j = $net->net_concurrency($j); TODO
85                 if ($lei->{opt}->{incremental} // 1) {
86                         $net->{incremental} = 1;
87                         $net->{-lms_ro} = $sto->search->lms // 0;
88                         if ($self->{-import_kw} && $net->{-lms_ro} &&
89                                         $net->{imap_order}) {
90                                 require PublicInbox::LeiImportKw;
91                                 $ikw = PublicInbox::LeiImportKw->new($lei);
92                                 $net->{each_old} = 1;
93                         }
94                 }
95         } else {
96                 my $nproc = $self->detect_nproc;
97                 $j = $nproc if $j > $nproc;
98         }
99         my $ops = {};
100         $lei->{auth}->op_merge($ops, $self) if $lei->{auth};
101         $self->{-wq_nr_workers} = $j // 1; # locked
102         $lei->{-eml_noisy} = 1;
103         (my $op_c, $ops) = $lei->workers_start($self, $j, $ops);
104         $lei->{wq1} = $self;
105         $lei->{-err_type} = 'non-fatal';
106         net_merge_all_done($self) unless $lei->{auth};
107         $lei->wait_wq_events($op_c, $ops);
108 }
109
110 sub lei_import { # the main "lei import" method
111         my ($lei, @inputs) = @_;
112         my $self = bless {}, __PACKAGE__;
113         do_import_index($self, $lei, @inputs);
114 }
115
116 sub _complete_import {
117         my ($lei, @argv) = @_;
118         my $sto = $lei->_lei_store or return;
119         my $lms = $sto->search->lms or return;
120         my $match_cb = $lei->complete_url_prepare(\@argv);
121         map { $match_cb->($_) } $lms->folders;
122 }
123
124 no warnings 'once';
125 *ipc_atfork_child = \&PublicInbox::LeiInput::input_only_atfork_child;
126 *net_merge_all_done = \&PublicInbox::LeiInput::input_only_net_merge_all_done;
127
128 # the following works even when LeiAuth is lazy-loaded
129 *net_merge_all = \&PublicInbox::LeiAuth::net_merge_all;
130 1;