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