1 # Copyright (C) 2021 all contributors <meta@public-inbox.org>
2 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
4 # front-end for the "lei export-kw" sub-command
5 package PublicInbox::LeiExportKw;
8 use parent qw(PublicInbox::IPC PublicInbox::LeiInput);
9 use Errno qw(EEXIST ENOENT);
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;
16 my ($md_kw, $unknown, @try);
17 if ($bn =~ s/:2,([a-zA-Z]*)\z//) {
18 ($md_kw, $unknown) = PublicInbox::MdirReader::flags2kw($1);
24 if ($self->{-merge_kw} && $md_kw) { # merging keywords is the default
25 @$sto_kw{keys %$md_kw} = values(%$md_kw);
28 PublicInbox::LeiToMail::kw2suffix([keys %$sto_kw], @$unknown);
29 my $dst = "$mdir/cur/$bn";
32 my $src = "$mdir/$d/$$id";
35 # we use link(2) + unlink(2) since rename(2) may
36 # inadvertently clobber if the "uniquefilename" part wasn't
38 if (link($src, $dst)) { # success
39 # unlink(2) may ENOENT from parallel invocation,
40 # ignore it, but not other serious errors
41 if (!unlink($src) and $! != ENOENT) {
42 $self->{lei}->child_error(1,
43 "E: unlink($src): $!");
45 $self->{lms}->mv_src("maildir:$mdir",
46 $oidbin, $id, $bn) or die;
47 return; # success anyways if link(2) worked
49 if ($! == ENOENT && !-e $src) { # some other process moved it
50 $self->{lms}->clear_src("maildir:$mdir", $id);
53 push @fail, $src if $! != EEXIST;
58 my $orig = '['.join('|', @fail).']';
59 $self->{lei}->child_error(1, "link($orig, $dst) ($oidhex): $e");
62 # overrides PublicInbox::LeiInput::input_path_url
64 my ($self, $input, @args) = @_;
65 my $lms = $self->{lms} //= $self->{lse}->lms;
67 if ($input =~ s/\Amaildir://i) {
68 require PublicInbox::LeiToMail; # kw2suffix
69 $lms->each_src("maildir:$input", \&export_kw_md, $self, $input);
75 my ($lei, @folders) = @_;
76 my $sto = $lei->_lei_store or return $lei->fail(<<EOM);
77 lei/store uninitialized, see lei-import(1)
79 my $lse = $sto->search;
80 my $lms = $lse->lms or return $lei->fail(<<EOM);
81 lei mail_sync uninitialized, see lei-import(1)
83 my $opt = $lei->{opt};
84 my $all = $opt->{all};
85 my @all = $lms->folders;
86 if (defined $all) { # --all=<local|remote>
87 my %x = map { $_ => $_ } split(/,/, $all);
88 my @ok = grep(defined, delete(@x{qw(local remote), ''}));
91 @no = (join(',', @no));
92 return $lei->fail(<<EOM);
93 --all=@no not accepted (must be `local' and/or `remote')
99 @inc = grep(!m!\A[a-z0-9\+]+://!i, @all);
100 } elsif ($ok eq 'remote') {
101 @inc = grep(m!\A[a-z0-9\+]+://!i, @all);
102 } elsif ($ok ne '') {
103 return $lei->fail("--all=$all not understood");
108 push(@folders, $_) unless $seen{$_}++;
111 return $lei->fail(<<EOM) if !@folders;
112 no --mail-sync folders known to lei
115 my %all = map { $_ => 1 } @all;
118 next if $all{$_}; # ok
119 if (-d "$_/new" && -d "$_/cur") {
120 my $d = 'maildir:'.$lei->rel2abs($_);
121 push(@no, $_) unless $all{$d};
127 my $no = join("\n\t", @no);
128 return $lei->fail(<<EOF) if @no;
129 No sync information for: $no
130 Run `lei ls-mail-sync' to display valid choices
133 my $self = bless { lse => $lse }, __PACKAGE__;
134 $lei->{opt}->{'mail-sync'} = 1; # for prepare_inputs
135 $self->prepare_inputs($lei, \@folders) or return;
136 my $j = $opt->{jobs} // scalar(@{$self->{inputs}}) || 1;
137 if (my @ro = grep(!/\A(?:maildir|imaps?):/, @folders)) {
138 return $lei->fail("cannot export to read-only folders: @ro");
140 if (my $net = $lei->{net}) {
141 require PublicInbox::NetWriter;
142 bless $net, 'PublicInbox::NetWriter';
145 my $m = $opt->{mode} // 'merge';
146 if ($m eq 'merge') { # default
147 $self->{-merge_kw} = 1;
148 } elsif ($m eq 'set') {
150 return $lei->fail(<<EOM);
151 --mode=$m not supported (`set' or `merge')
155 $lei->{auth}->op_merge($ops, $self) if $lei->{auth};
156 $self->{-wq_nr_workers} = $j // 1; # locked
157 (my $op_c, $ops) = $lei->workers_start($self, $j, $ops);
159 $lei->{-err_type} = 'non-fatal';
160 net_merge_all_done($self) unless $lei->{auth};
161 $op_c->op_wait_event($ops); # calls net_merge_all_done if $lei->{auth}
164 sub _complete_export_kw {
165 my ($lei, @argv) = @_;
166 my $sto = $lei->_lei_store or return;
167 my $lms = $sto->search->lms or return;
168 my $match_cb = $lei->complete_url_prepare(\@argv);
169 map { $match_cb->($_) } $lms->folders;
174 *ipc_atfork_child = \&PublicInbox::LeiInput::input_only_atfork_child;
175 *net_merge_all_done = \&PublicInbox::LeiInput::input_only_net_merge_all_done;
177 # the following works even when LeiAuth is lazy-loaded
178 *net_merge_all = \&PublicInbox::LeiAuth::net_merge_all;