]> Sergey Matveev's repositories - public-inbox.git/blob - lib/PublicInbox/LeiExportKw.pm
imap+nntp: share COMPRESS implementation
[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 use PublicInbox::Syscall qw(rename_noreplace);
11
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;
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         return if $bn eq $$id;
30         my $dst = "$mdir/cur/$bn";
31         my $lei = $self->{lei};
32         for my $d (@try) {
33                 my $src = "$mdir/$d/$$id";
34                 if (rename_noreplace($src, $dst)) { # success
35                         $self->{lms}->mv_src("maildir:$mdir",
36                                                 $oidbin, $id, $bn);
37                         return; # success
38                 } elsif ($! == EEXIST) { # lost race with lei/store?
39                         return;
40                 } elsif ($! != ENOENT) {
41                         $lei->child_error(1,
42                                 "E: rename_noreplace($src -> $dst): $!");
43                 } # else loop @try
44         }
45         my $e = $!;
46         # both tries failed
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);
52 }
53
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 ]);
58 }
59
60 # overrides PublicInbox::LeiInput::input_path_url
61 sub input_path_url {
62         my ($self, $input, @args) = @_;
63         $self->{lms}->lms_write_prepare;
64         if ($input =~ /\Amaildir:(.+)/i) {
65                 my $mdir = $1;
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                 my $mic = $self->{nwr}->mic_for_folder($uri);
71                 if ($mic && !$self->{nwr}->can_store_flags($mic)) {
72                         my $m = "$input does not support PERMANENTFLAGS";
73                         if (defined $self->{lei}->{opt}->{all}) {
74                                 $self->{lei}->qerr("# $m");
75                         } else { # set error code if user explicitly requested
76                                 $self->{lei}->child_error(0, "E: $m");
77                         }
78                         return;
79                 }
80                 if ($mic) {
81                         $self->{lms}->each_src($$uri, \&export_kw_imap,
82                                                 $self, $mic);
83                         $mic->expunge;
84                 } else {
85                         $self->{lei}->child_error(0, "$input unavailable: $@");
86                 }
87         } else { die "BUG: $input not supported" }
88 }
89
90 sub lei_export_kw {
91         my ($lei, @folders) = @_;
92         my $sto = $lei->_lei_store or return $lei->fail(<<EOM);
93 lei/store uninitialized, see lei-import(1)
94 EOM
95         my $lms = $lei->lms or return $lei->fail(<<EOM);
96 lei mail_sync uninitialized, see lei-import(1)
97 EOM
98         if (defined(my $all = $lei->{opt}->{all})) { # --all=<local|remote>
99                 $lms->group2folders($lei, $all, \@folders) or return;
100                 @folders = grep(/\A(?:maildir|imaps?):/i, @folders);
101         } else {
102                 $lms->arg2folder($lei, \@folders); # may die
103         }
104         $lms->lms_pause;
105         my $self = bless { lse => $sto->search, lms => $lms }, __PACKAGE__;
106         $lei->{opt}->{'mail-sync'} = 1; # for prepare_inputs
107         $self->prepare_inputs($lei, \@folders) or return;
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 = $lei->{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                 $self->{nwr}->{-skip_creat} = 1;
126         }
127         $lei->{-err_type} = 'non-fatal';
128         $lei->wq1_start($self);
129 }
130
131 sub _complete_export_kw {
132         my ($lei, @argv) = @_;
133         my $lms = $lei->lms or return ();
134         my $match_cb = $lei->complete_url_prepare(\@argv);
135         # filter-out read-only sources:
136         my @k = grep(m!(?:maildir|imaps?):!,
137                         $lms->folders($argv[-1] // undef, 1));
138         my @m = map { $match_cb->($_) } @k;
139         @m ? @m : @k;
140 }
141
142 no warnings 'once';
143
144 *ipc_atfork_child = \&PublicInbox::LeiInput::input_only_atfork_child;
145 *net_merge_all_done = \&PublicInbox::LeiInput::input_only_net_merge_all_done;
146
147 1;