]> Sergey Matveev's repositories - public-inbox.git/blob - lib/PublicInbox/LeiImport.pm
lei_mail_sync: rely on flock(2), avoid IPC
[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, $fl) = @_;
34         my ($folder, $bn) = ($f =~ m!\A(.+?)/(?:new|cur)/([^/]+)\z!) or
35                 die "BUG: $f was not from a Maildir?\n";
36         my $kw = PublicInbox::MdirReader::flags2kw($fl);
37         substr($folder, 0, 0) = 'maildir:'; # add prefix
38         my $lse = $self->{lse} //= $self->{lei}->{sto}->search;
39         my $lms = $self->{-lms_ro} //= $self->{lei}->lms; # may be 0 or undef
40         my $oidbin = $lms ? $lms->name_oidbin($folder, $bn) : undef;
41         my @docids = defined($oidbin) ? $lse->over->oidbin_exists($oidbin) : ();
42         my $vmd = $self->{-import_kw} ? { kw => $kw } : undef;
43         if (scalar @docids) {
44                 $lse->kw_changed(undef, $kw, \@docids) or return;
45         }
46         if (my $eml = eml_from_path($f)) {
47                 $vmd->{sync_info} = [ $folder, \$bn ] if $self->{-mail_sync};
48                 $self->input_eml_cb($eml, $vmd);
49         }
50 }
51
52 sub input_net_cb { # imap_each / nntp_each
53         my ($uri, $uid, $kw, $eml, $self) = @_;
54         if (defined $eml) {
55                 my $vmd = $self->{-import_kw} ? { kw => $kw } : undef;
56                 $vmd->{sync_info} = [ $$uri, $uid ] if $self->{-mail_sync};
57                 $self->input_eml_cb($eml, $vmd);
58         } elsif (my $ikw = $self->{lei}->{ikw}) { # old message, kw only
59                 # we send $uri as a bare SCALAR and not a URIimap ref to
60                 # reduce socket traffic:
61                 $ikw->wq_io_do('ck_update_kw', [], $$uri, $uid, $kw);
62         }
63 }
64
65 sub do_import_index ($$@) {
66         my ($self, $lei, @inputs) = @_;
67         my $sto = $lei->_lei_store(1);
68         $sto->write_prepare($lei);
69         $self->{-import_kw} = $lei->{opt}->{kw} // 1;
70         my $vmd_mod = $self->vmd_mod_extract(\@inputs);
71         return $lei->fail(join("\n", @{$vmd_mod->{err}})) if $vmd_mod->{err};
72         $self->{all_vmd} = $vmd_mod if scalar keys %$vmd_mod;
73         $lei->ale; # initialize for workers to read (before LeiPmdir->new)
74         $self->{-mail_sync} = $lei->{opt}->{'mail-sync'} // 1;
75         $self->prepare_inputs($lei, \@inputs) or return;
76
77         my $j = $lei->{opt}->{jobs} // 0;
78         $j =~ /\A([0-9]+),[0-9]+\z/ and $j = $1 + 0;
79         $j ||= scalar(@{$self->{inputs}}) || 1;
80         my $ikw;
81         my $net = $lei->{net};
82         if ($net) {
83                 # $j = $net->net_concurrency($j); TODO
84                 if ($lei->{opt}->{incremental} // 1) {
85                         $net->{incremental} = 1;
86                         $net->{-lms_ro} = $lei->lms // 0;
87                         if ($self->{-import_kw} && $net->{-lms_ro} &&
88                                         !$lei->{opt}->{'new-only'} &&
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         if ($lei->{opt}->{'new-only'} && (!$net || !$net->{imap_order})) {
100                 $lei->err('# --new-only is only for IMAP');
101         }
102         my $ops = {};
103         $lei->{auth}->op_merge($ops, $self) if $lei->{auth};
104         $lei->{-eml_noisy} = 1;
105         (my $op_c, $ops) = $lei->workers_start($self, $j, $ops);
106         $lei->{wq1} = $self;
107         $lei->{-err_type} = 'non-fatal';
108         net_merge_all_done($self) unless $lei->{auth};
109         $lei->wait_wq_events($op_c, $ops);
110 }
111
112 sub lei_import { # the main "lei import" method
113         my ($lei, @inputs) = @_;
114         my $self = bless {}, __PACKAGE__;
115         do_import_index($self, $lei, @inputs);
116 }
117
118 sub _complete_import {
119         my ($lei, @argv) = @_;
120         my $match_cb = $lei->complete_url_prepare(\@argv);
121         my @m = map { $match_cb->($_) } $lei->url_folder_cache->keys;
122         my %f = map { $_ => 1 } @m;
123         if (my $lms = $lei->lms) {
124                 @m = map { $match_cb->($_) } $lms->folders;
125                 @f{@m} = @m;
126         }
127         keys %f;
128 }
129
130 no warnings 'once';
131 *ipc_atfork_child = \&PublicInbox::LeiInput::input_only_atfork_child;
132 *net_merge_all_done = \&PublicInbox::LeiInput::input_only_net_merge_all_done;
133
134 1;