about summary refs log tree commit homepage
path: root/lib/PublicInbox/LeiExportKw.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/PublicInbox/LeiExportKw.pm')
-rw-r--r--lib/PublicInbox/LeiExportKw.pm147
1 files changed, 147 insertions, 0 deletions
diff --git a/lib/PublicInbox/LeiExportKw.pm b/lib/PublicInbox/LeiExportKw.pm
new file mode 100644
index 00000000..16f069da
--- /dev/null
+++ b/lib/PublicInbox/LeiExportKw.pm
@@ -0,0 +1,147 @@
+# Copyright (C) 2021 all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
+
+# front-end for the "lei export-kw" sub-command
+package PublicInbox::LeiExportKw;
+use strict;
+use v5.10.1;
+use parent qw(PublicInbox::IPC PublicInbox::LeiInput);
+use Errno qw(EEXIST ENOENT);
+use PublicInbox::Syscall qw(rename_noreplace);
+
+sub export_kw_md { # LeiMailSync->each_src callback
+        my ($oidbin, $id, $self, $mdir) = @_;
+        my $sto_kw = $self->{lse}->oidbin_keywords($oidbin) or return;
+        my $bn = $$id;
+        my ($md_kw, $unknown, @try);
+        if ($bn =~ s/:2,([a-zA-Z]*)\z//) {
+                ($md_kw, $unknown) = PublicInbox::MdirReader::flags2kw($1);
+                @try = qw(cur new);
+        } else {
+                $unknown = [];
+                @try = qw(new cur);
+        }
+        if ($self->{-merge_kw} && $md_kw) { # merging keywords is the default
+                @$sto_kw{keys %$md_kw} = values(%$md_kw);
+        }
+        $bn .= ':2,'.
+                PublicInbox::LeiToMail::kw2suffix([keys %$sto_kw], @$unknown);
+        return if $bn eq $$id;
+        my $dst = "$mdir/cur/$bn";
+        my $lei = $self->{lei};
+        for my $d (@try) {
+                my $src = "$mdir/$d/$$id";
+                if (rename_noreplace($src, $dst)) { # success
+                        $self->{lms}->mv_src("maildir:$mdir",
+                                                $oidbin, $id, $bn);
+                        return; # success
+                } elsif ($! == EEXIST) { # lost race with lei/store?
+                        return;
+                } elsif ($! != ENOENT) {
+                        $lei->child_error(0,
+                                "E: rename_noreplace($src -> $dst): $!");
+                } # else loop @try
+        }
+        my $e = $!;
+        # both tries failed
+        my $oidhex = unpack('H*', $oidbin);
+        my $src = "$mdir/{".join(',', @try)."}/$$id";
+        $lei->child_error(0, "rename_noreplace($src -> $dst) ($oidhex): $e");
+        for (@try) { return if -e "$mdir/$_/$$id" }
+        $self->{lms}->clear_src("maildir:$mdir", $id);
+}
+
+sub export_kw_imap { # LeiMailSync->each_src callback
+        my ($oidbin, $id, $self, $mic) = @_;
+        my $sto_kw = $self->{lse}->oidbin_keywords($oidbin) or return;
+        $self->{imap_mod_kw}->($self->{nwr}, $mic, $id, [ keys %$sto_kw ]);
+}
+
+# overrides PublicInbox::LeiInput::input_path_url
+sub input_path_url {
+        my ($self, $input, @args) = @_;
+        $self->{lms}->lms_write_prepare;
+        if ($input =~ /\Amaildir:(.+)/i) {
+                my $mdir = $1;
+                require PublicInbox::LeiToMail; # kw2suffix
+                $self->{lms}->each_src($input, \&export_kw_md, $self, $mdir);
+        } elsif ($input =~ m!\Aimaps?://!i) {
+                my $uri = PublicInbox::URIimap->new($input);
+                my $mic = $self->{nwr}->mic_for_folder($uri);
+                if ($mic && !$self->{nwr}->can_store_flags($mic)) {
+                        my $m = "$input does not support PERMANENTFLAGS";
+                        if (defined $self->{lei}->{opt}->{all}) {
+                                $self->{lei}->qerr("# $m");
+                        } else { # set error code if user explicitly requested
+                                $self->{lei}->child_error(0, "E: $m");
+                        }
+                        return;
+                }
+                if ($mic) {
+                        $self->{lms}->each_src($$uri, \&export_kw_imap,
+                                                $self, $mic);
+                        $mic->expunge;
+                } else {
+                        $self->{lei}->child_error(0, "$input unavailable: $@");
+                }
+        } else { die "BUG: $input not supported" }
+}
+
+sub lei_export_kw {
+        my ($lei, @folders) = @_;
+        my $sto = $lei->_lei_store or return $lei->fail(<<EOM);
+lei/store uninitialized, see lei-import(1)
+EOM
+        my $lms = $lei->lms or return $lei->fail(<<EOM);
+lei mail_sync uninitialized, see lei-import(1)
+EOM
+        if (defined(my $all = $lei->{opt}->{all})) { # --all=<local|remote>
+                $lms->group2folders($lei, $all, \@folders) or return;
+                @folders = grep(/\A(?:maildir|imaps?):/i, @folders);
+        } else {
+                $lms->arg2folder($lei, \@folders); # may die
+        }
+        $lms->lms_pause;
+        my $self = bless { lse => $sto->search, lms => $lms }, __PACKAGE__;
+        $lei->{opt}->{'mail-sync'} = 1; # for prepare_inputs
+        $self->prepare_inputs($lei, \@folders) or return;
+        if (my @ro = grep(!/\A(?:maildir|imaps?):/i, @folders)) {
+                return $lei->fail("cannot export to read-only folders: @ro");
+        }
+        my $m = $lei->{opt}->{mode} // 'merge';
+        if ($m eq 'merge') { # default
+                $self->{-merge_kw} = 1;
+        } elsif ($m eq 'set') {
+        } else {
+                return $lei->fail(<<EOM);
+--mode=$m not supported (`set' or `merge')
+EOM
+        }
+        if (my $net = $lei->{net}) {
+                require PublicInbox::NetWriter;
+                $self->{nwr} = bless $net, 'PublicInbox::NetWriter';
+                $self->{imap_mod_kw} = $net->can($self->{-merge_kw} ?
+                                        'imap_add_kw' : 'imap_set_kw');
+                $self->{nwr}->{-skip_creat} = 1;
+        }
+        $lei->{-err_type} = 'non-fatal';
+        $lei->wq1_start($self);
+}
+
+sub _complete_export_kw {
+        my ($lei, @argv) = @_;
+        my $lms = $lei->lms or return ();
+        my $match_cb = $lei->complete_url_prepare(\@argv);
+        # filter-out read-only sources:
+        my @k = grep(m!(?:maildir|imaps?):!,
+                        $lms->folders($argv[-1] // undef, 1));
+        my @m = map { $match_cb->($_) } @k;
+        @m ? @m : @k;
+}
+
+no warnings 'once';
+
+*ipc_atfork_child = \&PublicInbox::LeiInput::input_only_atfork_child;
+*net_merge_all_done = \&PublicInbox::LeiInput::input_only_net_merge_all_done;
+
+1;