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);
10 use PublicInbox::Syscall qw(rename_noreplace);
12 sub export_kw_md { # LeiMailSync->each_src callback
13 my ($oidbin, $id, $self, $mdir) = @_;
14 my $sto_kw = $self->{lse}->oidbin_keywords($oidbin) 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 return if $bn eq $$id;
30 my $dst = "$mdir/cur/$bn";
31 my $lei = $self->{lei};
33 my $src = "$mdir/$d/$$id";
34 if (rename_noreplace($src, $dst)) { # success
35 $self->{lms}->mv_src("maildir:$mdir",
38 } elsif ($! == EEXIST) { # lost race with lei/store?
40 } elsif ($! != ENOENT) {
42 "E: rename_noreplace($src -> $dst): $!");
47 my $oidhex = unpack('H*', $oidbin);
48 my $src = "$mdir/{".join(',', @try)."}/$$id";
49 $lei->child_error(1, "rename_noreplace($src -> $dst) ($oidhex): $e");
50 for (@try) { return if -e "$mdir/$_/$$id" }
51 $self->{lms}->clear_src("maildir:$mdir", $id);
54 sub export_kw_imap { # LeiMailSync->each_src callback
55 my ($oidbin, $id, $self, $mic) = @_;
56 my $sto_kw = $self->{lse}->oidbin_keywords($oidbin) or return;
57 $self->{imap_mod_kw}->($self->{nwr}, $mic, $id, [ keys %$sto_kw ]);
60 # overrides PublicInbox::LeiInput::input_path_url
62 my ($self, $input, @args) = @_;
63 $self->{lms}->lms_write_prepare;
64 if ($input =~ /\Amaildir:(.+)/i) {
66 require PublicInbox::LeiToMail; # kw2suffix
67 $self->{lms}->each_src($input, \&export_kw_md, $self, $mdir);
68 } elsif ($input =~ m!\Aimaps?://!i) {
69 my $uri = PublicInbox::URIimap->new($input);
70 if (my $mic = $self->{nwr}->mic_for_folder($uri)) {
71 $self->{lms}->each_src($$uri, \&export_kw_imap,
75 $self->{lei}->child_error(0, "$input unavailable: $@");
77 } else { die "BUG: $input not supported" }
81 my ($lei, @folders) = @_;
82 my $sto = $lei->_lei_store or return $lei->fail(<<EOM);
83 lei/store uninitialized, see lei-import(1)
85 my $lms = $lei->lms or return $lei->fail(<<EOM);
86 lei mail_sync uninitialized, see lei-import(1)
88 if (defined(my $all = $lei->{opt}->{all})) { # --all=<local|remote>
89 $lms->group2folders($lei, $all, \@folders) or return;
90 @folders = grep(/\A(?:maildir|imaps?):/i, @folders);
92 $lms->arg2folder($lei, \@folders); # may die
95 my $self = bless { lse => $sto->search, lms => $lms }, __PACKAGE__;
96 $lei->{opt}->{'mail-sync'} = 1; # for prepare_inputs
97 $self->prepare_inputs($lei, \@folders) or return;
98 if (my @ro = grep(!/\A(?:maildir|imaps?):/i, @folders)) {
99 return $lei->fail("cannot export to read-only folders: @ro");
101 my $m = $lei->{opt}->{mode} // 'merge';
102 if ($m eq 'merge') { # default
103 $self->{-merge_kw} = 1;
104 } elsif ($m eq 'set') {
106 return $lei->fail(<<EOM);
107 --mode=$m not supported (`set' or `merge')
110 if (my $net = $lei->{net}) {
111 require PublicInbox::NetWriter;
112 $self->{nwr} = bless $net, 'PublicInbox::NetWriter';
113 $self->{imap_mod_kw} = $net->can($self->{-merge_kw} ?
114 'imap_add_kw' : 'imap_set_kw');
115 $self->{nwr}->{-skip_creat} = 1;
118 $lei->{auth}->op_merge($ops, $self) if $lei->{auth};
119 (my $op_c, $ops) = $lei->workers_start($self, 1, $ops);
121 $lei->{-err_type} = 'non-fatal';
122 net_merge_all_done($self) unless $lei->{auth};
123 $lei->wait_wq_events($op_c, $ops); # net_merge_all_done if !{auth}
126 sub _complete_export_kw {
127 my ($lei, @argv) = @_;
128 my $lms = $lei->lms or return ();
129 my $match_cb = $lei->complete_url_prepare(\@argv);
130 # filter-out read-only sources:
131 my @k = grep(!m!(?://;AUTH=ANONYMOUS\@|\A(?:nntps?|s?news)://)!,
132 $lms->folders($argv[-1] // undef, 1));
133 my @m = map { $match_cb->($_) } @k;
139 *ipc_atfork_child = \&PublicInbox::LeiInput::input_only_atfork_child;
140 *net_merge_all_done = \&PublicInbox::LeiInput::input_only_net_merge_all_done;