]> Sergey Matveev's repositories - public-inbox.git/blobdiff - lib/PublicInbox/NetReader.pm
lei import: --incremental default for NNTP and IMAP
[public-inbox.git] / lib / PublicInbox / NetReader.pm
index 2a45321769b19230e37ec2e1e23d6f9313642994..c7b43f014f6bd31d5c2e435f8ef3b71640cbfa17 100644 (file)
@@ -7,14 +7,15 @@ use strict;
 use v5.10.1;
 use parent qw(Exporter PublicInbox::IPC);
 use PublicInbox::Eml;
-
 our %IMAPflags2kw = map {; "\\\u$_" => $_ } qw(seen answered flagged draft);
+$IMAPflags2kw{'$Forwarded'} = 'forwarded';  # RFC 5550
+
+our @EXPORT = qw(uri_section imap_uri nntp_uri);
 
-# TODO: trim this down, this is huge
-our @EXPORT = qw(uri_new uri_section
-               nn_new imap_uri nntp_uri
-               cfg_bool cfg_intvl imap_common_init nntp_common_init
-               );
+sub ndump {
+       require Data::Dumper;
+       Data::Dumper->new(\@_)->Useqq(1)->Terse(1)->Dump;
+}
 
 # returns the git config section name, e.g [imap "imaps://user@example.com"]
 # without the mailbox, so we can share connections between different inboxes
@@ -94,15 +95,6 @@ sub mic_for { # mic = Mail::IMAPClient
        $mic;
 }
 
-sub uri_new {
-       my ($url) = @_;
-       require URI;
-
-       # URI::snews exists, URI::nntps does not, so use URI::snews
-       $url =~ s!\Anntps://!snews://!i;
-       URI->new($url);
-}
-
 # Net::NNTP doesn't support CAPABILITIES, yet
 sub try_starttls ($) {
        my ($host) = @_;
@@ -273,10 +265,11 @@ sub imap_common_init ($;$) {
        my $mics = {}; # schema://authority => IMAPClient obj
        for my $uri (@{$self->{imap_order}}) {
                my $sec = uri_section($uri);
-               $mics->{$sec} //= mic_for($self, "$sec/", $mic_args, $lei);
+               my $mic = $mics->{$sec} //=
+                               mic_for($self, "$sec/", $mic_args, $lei) //
+                               die "Unable to continue\n";
                next unless $self->isa('PublicInbox::NetWriter');
                my $dst = $uri->mailbox // next;
-               my $mic = $mics->{$sec};
                next if $mic->exists($dst); # already exists
                $mic->create($dst) or die "CREATE $dst failed <$uri>: $@";
        }
@@ -359,13 +352,34 @@ sub _imap_do_msg ($$$$$) {
        $$raw =~ s/\r\n/\n/sg;
        my $kw = [];
        for my $f (split(/ /, $flags)) {
-               my $k = $IMAPflags2kw{$f} // next; # TODO: X-Label?
-               push @$kw, $k;
+               if (my $k = $IMAPflags2kw{$f}) {
+                       push @$kw, $k;
+               } elsif ($f eq "\\Recent") { # not in JMAP
+               } elsif ($f eq "\\Deleted") { # not in JMAP
+                       return;
+               } elsif ($self->{verbose}) {
+                       warn "# unknown IMAP flag $f <$uri;uid=$uid>\n";
+               }
        }
+       @$kw = sort @$kw; # for all UI/UX purposes
        my ($eml_cb, @args) = @{$self->{eml_each}};
        $eml_cb->($uri, $uid, $kw, PublicInbox::Eml->new($raw), @args);
 }
 
+sub run_commit_cb ($) {
+       my ($self) = @_;
+       my $cmt_cb_args = $self->{on_commit} or return;
+       my ($cb, @args) = @$cmt_cb_args;
+       $cb->(@args);
+}
+
+sub _itrk ($$) {
+       my ($self, $uri) = @_;
+       return unless $self->{incremental};
+       # itrk_fn is set by lei
+       PublicInbox::IMAPTracker->new($$uri, $self->{itrk_fn});
+}
+
 sub _imap_fetch_all ($$$) {
        my ($self, $mic, $uri) = @_;
        my $sec = uri_section($uri);
@@ -382,8 +396,7 @@ sub _imap_fetch_all ($$$) {
                return "E: $uri cannot get UIDVALIDITY";
        $r_uidnext //= $mic->uidnext($mbx) //
                return "E: $uri cannot get UIDNEXT";
-       my $itrk = $self->{incremental} ?
-                       PublicInbox::IMAPTracker->new($$uri) : 0;
+       my $itrk = _itrk($self, $uri);
        my ($l_uidval, $l_uid) = $itrk ? $itrk->get_last : ();
        $l_uidval //= $r_uidval; # first time
        $l_uid //= 0;
@@ -414,8 +427,10 @@ sub _imap_fetch_all ($$$) {
                # I wish "UID FETCH $START:*" could work, but:
                # 1) servers do not need to return results in any order
                # 2) Mail::IMAPClient doesn't offer a streaming API
-               $uids = $mic->search("UID $l_uid:*") or
+               unless ($uids = $mic->search("UID $l_uid:*")) {
+                       return if $!{EINTR} && $self->{quit};
                        return "E: $uri UID SEARCH $l_uid:* error: $!";
+               }
                return if scalar(@$uids) == 0;
 
                # RFC 3501 doesn't seem to indicate order of UID SEARCH
@@ -437,6 +452,7 @@ sub _imap_fetch_all ($$$) {
                        local $0 = "UID:$batch $mbx $sec";
                        my $r = $mic->fetch_hash($batch, $req, 'FLAGS');
                        unless ($r) { # network error?
+                               last if $!{EINTR} && $self->{quit};
                                $err = "E: $uri UID FETCH $batch error: $!";
                                last;
                        }
@@ -451,6 +467,7 @@ sub _imap_fetch_all ($$$) {
                        }
                        last if $self->{quit};
                }
+               run_commit_cb($self);
                $itrk->update_last($r_uidval, $last_uid) if $itrk;
        } until ($err || $self->{quit});
        $err;
@@ -490,7 +507,7 @@ sub imap_each {
                local $self->{eml_each} = [ $eml_cb, @args ];
                $err = _imap_fetch_all($self, $mic, $uri);
        } else {
-               $err = "E: not connected: $!";
+               $err = "E: <$uri> not connected: $!";
        }
        warn $err if $err;
        $mic;
@@ -525,15 +542,14 @@ sub _nntp_fetch_all ($$$) {
        my $sec = uri_section($uri);
        my ($nr, $beg, $end) = $nn->group($group);
        unless (defined($nr)) {
-               chomp(my $msg = $nn->message);
+               my $msg = ndump($nn->message);
                return "E: GROUP $group <$sec> $msg";
        }
 
        # IMAPTracker is also used for tracking NNTP, UID == article number
        # LIST.ACTIVE can get the equivalent of UIDVALIDITY, but that's
        # expensive.  So we assume newsgroups don't change:
-       my $itrk = $self->{incremental} ?
-                       PublicInbox::IMAPTracker->new($$uri) : 0;
+       my $itrk = _itrk($self, $uri);
        my (undef, $l_art) = $itrk ? $itrk->get_last : ();
 
        # allow users to specify articles to refetch
@@ -545,22 +561,22 @@ sub _nntp_fetch_all ($$$) {
                return if $l_art >= $end; # nothing to do
                $beg = $l_art + 1;
        }
-       my ($err, $art);
+       my ($err, $art, $last_art, $kw); # kw stays undef, no keywords in NNTP
        unless ($self->{quiet}) {
                warn "# $uri fetching ARTICLE $beg..$end\n";
        }
-       my $last_art;
        my $n = $self->{max_batch};
        for ($beg..$end) {
                last if $self->{quit};
                $art = $_;
                if (--$n < 0) {
+                       run_commit_cb($self);
                        $itrk->update_last(0, $last_art) if $itrk;
                        $n = $self->{max_batch};
                }
                my $raw = $nn->article($art);
                unless (defined($raw)) {
-                       my $msg = $nn->message;
+                       my $msg = ndump($nn->message);
                        if ($nn->code == 421) { # pseudo response from Net::Cmd
                                $err = "E: $msg";
                                last;
@@ -572,9 +588,10 @@ sub _nntp_fetch_all ($$$) {
                $raw = join('', @$raw);
                $raw =~ s/\r\n/\n/sg;
                my ($eml_cb, @args) = @{$self->{eml_each}};
-               $eml_cb->($uri, $art, [], PublicInbox::Eml->new(\$raw), @args);
+               $eml_cb->($uri, $art, $kw, PublicInbox::Eml->new(\$raw), @args);
                $last_art = $art;
        }
+       run_commit_cb($self);
        $itrk->update_last(0, $last_art) if $itrk;
        $err;
 }
@@ -585,12 +602,13 @@ sub nntp_each {
        my $sec = uri_section($uri);
        local $0 = $uri->group ." $sec";
        my $nn = nn_get($self, $uri);
+       return if $self->{quit};
        my $err;
        if ($nn) {
                local $self->{eml_each} = [ $eml_cb, @args ];
                $err = _nntp_fetch_all($self, $nn, $uri);
        } else {
-               $err = "E: not connected: $!";
+               $err = "E: <$uri> not connected: $!";
        }
        warn $err if $err;
        $nn;