]> Sergey Matveev's repositories - public-inbox.git/blob - lib/PublicInbox/LeiConvert.pm
8d3b221a6eb9077869e812eee5cc84e2735b0240
[public-inbox.git] / lib / PublicInbox / LeiConvert.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 convert" sub-command
5 package PublicInbox::LeiConvert;
6 use strict;
7 use v5.10.1;
8 use parent qw(PublicInbox::IPC);
9 use PublicInbox::Eml;
10 use PublicInbox::LeiStore;
11 use PublicInbox::LeiOverview;
12
13 sub mbox_cb {
14         my ($eml, $self) = @_;
15         my $kw = PublicInbox::MboxReader::mbox_keywords($eml);
16         $eml->header_set($_) for qw(Status X-Status);
17         $self->{wcb}->(undef, { kw => $kw }, $eml);
18 }
19
20 sub net_cb { # callback for ->imap_each, ->nntp_each
21         my (undef, undef, $kw, $eml, $self) = @_; # @_[0,1]: url + uid ignored
22         $self->{wcb}->(undef, { kw => $kw }, $eml);
23 }
24
25 sub mdir_cb {
26         my ($f, $kw, $eml, $self) = @_;
27         $self->{wcb}->(undef, { kw => $kw }, $eml);
28 }
29
30 sub convert_fh ($$$$) {
31         my ($self, $ifmt, $fh, $name) = @_;
32         if ($ifmt eq 'eml') {
33                 my $buf = do { local $/; <$fh> } //
34                         return $self->{lei}->child_error(1 << 8, <<"");
35 error reading $name: $!
36
37                 my $eml = PublicInbox::Eml->new(\$buf);
38                 $self->{wcb}->(undef, { kw => [] }, $eml);
39         } else {
40                 PublicInbox::MboxReader->$ifmt($fh, \&mbox_cb, $self);
41         }
42 }
43
44 sub do_convert { # via wq_do
45         my ($self) = @_;
46         my $lei = $self->{lei};
47         my $in_fmt = $lei->{opt}->{'in-format'};
48         my $mics;
49         if (my $stdin = delete $self->{0}) {
50                 convert_fh($self, $in_fmt, $stdin, '<stdin>');
51         }
52         for my $input (@{$self->{inputs}}) {
53                 my $ifmt = lc($in_fmt // '');
54                 if ($input =~ m!\Aimaps?://!) {
55                         $lei->{net}->imap_each($input, \&net_cb, $self);
56                         next;
57                 } elsif ($input =~ m!\A(?:nntps?|s?news)://!) {
58                         $lei->{net}->nntp_each($input, \&net_cb, $self);
59                         next;
60                 } elsif ($input =~ s!\A([a-z0-9]+):!!i) {
61                         $ifmt = lc $1;
62                 }
63                 if (-f $input) {
64                         my $m = $lei->{opt}->{'lock'} //
65                                         ($ifmt eq 'eml' ? ['none'] :
66                                         PublicInbox::MboxLock->defaults);
67                         my $mbl = PublicInbox::MboxLock->acq($input, 0, $m);
68                         convert_fh($self, $ifmt, $mbl->{fh}, $input);
69                 } elsif (-d _) {
70                         PublicInbox::MdirReader::maildir_each_eml($input,
71                                                         \&mdir_cb, $self);
72                 } else {
73                         die "BUG: $input unhandled"; # should've failed earlier
74                 }
75         }
76         delete $lei->{1};
77         delete $self->{wcb}; # commit
78 }
79
80 sub lei_convert { # the main "lei convert" method
81         my ($lei, @inputs) = @_;
82         my $opt = $lei->{opt};
83         $opt->{kw} //= 1;
84         my $self = $lei->{cnv} = bless {}, __PACKAGE__;
85         my $in_fmt = $opt->{'in-format'};
86         my (@f, @d);
87         $opt->{dedupe} //= 'none';
88         my $ovv = PublicInbox::LeiOverview->new($lei, 'out-format');
89         $lei->{l2m} or return
90                 $lei->fail("output not specified or is not a mail destination");
91         my $net = $lei->{net}; # NetWriter may be created by l2m
92         $opt->{augment} = 1 unless $ovv->{dst} eq '/dev/stdout';
93         if ($opt->{stdin}) {
94                 @inputs and return $lei->fail("--stdin and @inputs do not mix");
95                 $lei->check_input_format(undef) or return;
96                 $self->{0} = $lei->{0};
97         }
98         # e.g. Maildir:/home/user/Mail/ or imaps://example.com/INBOX
99         for my $input (@inputs) {
100                 my $input_path = $input;
101                 if ($input =~ m!\A(?:imaps?|nntps?|s?news)://!i) {
102                         require PublicInbox::NetReader;
103                         $net //= PublicInbox::NetReader->new;
104                         $net->add_url($input);
105                 } elsif ($input_path =~ s/\A([a-z0-9]+)://is) {
106                         my $ifmt = lc $1;
107                         if (($in_fmt // $ifmt) ne $ifmt) {
108                                 return $lei->fail(<<"");
109 --in-format=$in_fmt and `$ifmt:' conflict
110
111                         }
112                         if (-f $input_path) {
113                                 require PublicInbox::MboxLock;
114                                 require PublicInbox::MboxReader;
115                                 PublicInbox::MboxReader->can($ifmt) or return
116                                         $lei->fail("$ifmt not supported");
117                         } elsif (-d _) {
118                                 require PublicInbox::MdirReader;
119                                 $ifmt eq 'maildir' or return
120                                         $lei->fail("$ifmt not supported");
121                         } else {
122                                 return $lei->fail("Unable to handle $input");
123                         }
124                 } elsif (-f $input) { push @f, $input }
125                 elsif (-d _) { push @d, $input }
126                 else { return $lei->fail("Unable to handle $input") }
127         }
128         if (@f) { $lei->check_input_format(\@f) or return }
129         if (@d) { # TODO: check for MH vs Maildir, here
130                 require PublicInbox::MdirReader;
131         }
132         $self->{inputs} = \@inputs;
133         if ($net) {
134                 if (my $err = $net->errors) {
135                         return $lei->fail($err);
136                 }
137                 $net->{quiet} = $opt->{quiet};
138                 $lei->{net} //= $net;
139         }
140         my $op = $lei->workers_start($self, 'lei_convert', 1, {
141                 '' => [ $lei->can('dclose'), $lei ]
142         });
143         $self->wq_io_do('do_convert', []);
144         $self->wq_close(1);
145         while ($op && $op->{sock}) { $op->event_step }
146 }
147
148 sub ipc_atfork_child {
149         my ($self) = @_;
150         my $lei = $self->{lei};
151         $lei->lei_atfork_child;
152         my $l2m = delete $lei->{l2m};
153         if (my $net = $lei->{net}) { # may prompt user once
154                 $net->{mics_cached} = $net->imap_common_init($lei);
155                 $net->{nn_cached} = $net->nntp_common_init($lei);
156         }
157         $SIG{__WARN__} = PublicInbox::Eml::warn_ignore_cb();
158         $l2m->pre_augment($lei);
159         $l2m->do_augment($lei);
160         $l2m->post_augment($lei);
161         $self->{wcb} = $l2m->write_cb($lei);
162         $self->SUPER::ipc_atfork_child;
163 }
164
165 1;