]> Sergey Matveev's repositories - public-inbox.git/blob - lib/PublicInbox/LeiExportKw.pm
lei_mail_sync: hoist out --all handling from export-kw
[public-inbox.git] / lib / PublicInbox / LeiExportKw.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 export-kw" sub-command
5 package PublicInbox::LeiExportKw;
6 use strict;
7 use v5.10.1;
8 use parent qw(PublicInbox::IPC PublicInbox::LeiInput);
9 use Errno qw(EEXIST ENOENT);
10
11 sub export_kw_md { # LeiMailSync->each_src callback
12         my ($oidbin, $id, $self, $mdir) = @_;
13         my $oidhex = unpack('H*', $oidbin);
14         my $sto_kw = $self->{lse}->oid_keywords($oidhex) or return;
15         my $bn = $$id;
16         my ($md_kw, $unknown, @try);
17         if ($bn =~ s/:2,([a-zA-Z]*)\z//) {
18                 ($md_kw, $unknown) = PublicInbox::MdirReader::flags2kw($1);
19                 @try = qw(cur new);
20         } else {
21                 $unknown = [];
22                 @try = qw(new cur);
23         }
24         if ($self->{-merge_kw} && $md_kw) { # merging keywords is the default
25                 @$sto_kw{keys %$md_kw} = values(%$md_kw);
26         }
27         $bn .= ':2,'.
28                 PublicInbox::LeiToMail::kw2suffix([keys %$sto_kw], @$unknown);
29         my $dst = "$mdir/cur/$bn";
30         my @fail;
31         my $lei = $self->{lei};
32         for my $d (@try) {
33                 my $src = "$mdir/$d/$$id";
34                 next if $src eq $dst;
35
36                 # we use link(2) + unlink(2) since rename(2) may
37                 # inadvertently clobber if the "uniquefilename" part wasn't
38                 # actually unique.
39                 if (link($src, $dst)) { # success
40                         # unlink(2) may ENOENT from parallel invocation,
41                         # ignore it, but not other serious errors
42                         if (!unlink($src) and $! != ENOENT) {
43                                 $lei->child_error(1, "E: unlink($src): $!");
44                         }
45                         $lei->{sto}->ipc_do('lms_mv_src', "maildir:$mdir",
46                                                 $oidbin, $id, $bn);
47                         return; # success anyways if link(2) worked
48                 }
49                 if ($! == ENOENT && !-e $src) { # some other process moved it
50                         $lei->{sto}->ipc_do('lms_clear_src',
51                                                 "maildir:$mdir", $id);
52                         next;
53                 }
54                 push @fail, $src if $! != EEXIST;
55         }
56         return unless @fail;
57         # both tries failed
58         my $e = $!;
59         my $orig = '['.join('|', @fail).']';
60         $lei->child_error(1, "link($orig, $dst) ($oidhex): $e");
61 }
62
63 sub export_kw_imap { # LeiMailSync->each_src callback
64         my ($oidbin, $id, $self, $mic) = @_;
65         my $oidhex = unpack('H*', $oidbin);
66         my $sto_kw = $self->{lse}->oid_keywords($oidhex) or return;
67         $self->{imap_mod_kw}->($self->{nwr}, $mic, $id, [ keys %$sto_kw ]);
68 }
69
70 # overrides PublicInbox::LeiInput::input_path_url
71 sub input_path_url {
72         my ($self, $input, @args) = @_;
73         my $lms = $self->{-lms_ro} //= $self->{lse}->lms;
74         if ($input =~ /\Amaildir:(.+)/i) {
75                 my $mdir = $1;
76                 require PublicInbox::LeiToMail; # kw2suffix
77                 $lms->each_src($input, \&export_kw_md, $self, $mdir);
78         } elsif ($input =~ m!\Aimaps?://!i) {
79                 my $uri = PublicInbox::URIimap->new($input);
80                 my $mic = $self->{nwr}->mic_for_folder($uri);
81                 $lms->each_src($$uri, \&export_kw_imap, $self, $mic);
82                 $mic->expunge;
83         } else { die "BUG: $input not supported" }
84         my $wait = $self->{lei}->{sto}->ipc_do('done');
85 }
86
87 sub lei_export_kw {
88         my ($lei, @folders) = @_;
89         my $sto = $lei->_lei_store or return $lei->fail(<<EOM);
90 lei/store uninitialized, see lei-import(1)
91 EOM
92         my $lse = $sto->search;
93         my $lms = $lse->lms or return $lei->fail(<<EOM);
94 lei mail_sync uninitialized, see lei-import(1)
95 EOM
96         my $opt = $lei->{opt};
97         if (defined(my $all = $opt->{all})) { # --all=<local|remote>
98                 $lms->group2folders($lei, $all, \@folders) or return;
99         } else {
100                 my $err = $lms->arg2folder($lei, \@folders);
101                 $lei->qerr(@{$err->{qerr}}) if $err->{qerr};
102                 return $lei->fail($err->{fail}) if $err->{fail};
103         }
104         my $self = bless { lse => $lse }, __PACKAGE__;
105         $lei->{opt}->{'mail-sync'} = 1; # for prepare_inputs
106         $self->prepare_inputs($lei, \@folders) or return;
107         my $j = $opt->{jobs} // scalar(@{$self->{inputs}}) || 1;
108         if (my @ro = grep(!/\A(?:maildir|imaps?):/i, @folders)) {
109                 return $lei->fail("cannot export to read-only folders: @ro");
110         }
111         my $m = $opt->{mode} // 'merge';
112         if ($m eq 'merge') { # default
113                 $self->{-merge_kw} = 1;
114         } elsif ($m eq 'set') {
115         } else {
116                 return $lei->fail(<<EOM);
117 --mode=$m not supported (`set' or `merge')
118 EOM
119         }
120         if (my $net = $lei->{net}) {
121                 require PublicInbox::NetWriter;
122                 $self->{nwr} = bless $net, 'PublicInbox::NetWriter';
123                 $self->{imap_mod_kw} = $net->can($self->{-merge_kw} ?
124                                         'imap_add_kw' : 'imap_set_kw');
125         }
126         undef $lms; # for fork
127         my $ops = {};
128         $sto->write_prepare($lei);
129         $lei->{auth}->op_merge($ops, $self) if $lei->{auth};
130         $self->{-wq_nr_workers} = $j // 1; # locked
131         (my $op_c, $ops) = $lei->workers_start($self, $j, $ops);
132         $lei->{wq1} = $self;
133         $lei->{-err_type} = 'non-fatal';
134         net_merge_all_done($self) unless $lei->{auth};
135         $lei->wait_wq_events($op_c, $ops); # net_merge_all_done if !{auth}
136 }
137
138 sub _complete_export_kw {
139         my ($lei, @argv) = @_;
140         my $sto = $lei->_lei_store or return;
141         my $lms = $sto->search->lms or return;
142         my $match_cb = $lei->complete_url_prepare(\@argv);
143         map { $match_cb->($_) } $lms->folders;
144 }
145
146 no warnings 'once';
147
148 *ipc_atfork_child = \&PublicInbox::LeiInput::input_only_atfork_child;
149 *net_merge_all_done = \&PublicInbox::LeiInput::input_only_net_merge_all_done;
150
151 # the following works even when LeiAuth is lazy-loaded
152 *net_merge_all = \&PublicInbox::LeiAuth::net_merge_all;
153
154 1;