From e51c8899d67e5d86a868e1adced55a6c72113daa Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Sat, 5 Mar 2016 18:36:07 +0100 Subject: pullimap: add support for IMAP IDLE (RFC 2177). --- pullimap | 81 ++++++++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 48 insertions(+), 33 deletions(-) (limited to 'pullimap') diff --git a/pullimap b/pullimap index f9b9d0d..2c9b45d 100755 --- a/pullimap +++ b/pullimap @@ -47,7 +47,7 @@ sub usage(;$) { exit $rv; } -usage(1) unless GetOptions(\%CONFIG, qw/config=s quiet|q debug help|h/); +usage(1) unless GetOptions(\%CONFIG, qw/config=s quiet|q debug help|h idle:i/); usage(0) if $CONFIG{help}; usage(1) unless $#ARGV == 0 and $ARGV[0] ne '_'; @@ -225,10 +225,47 @@ sub smtp_send(@) { # the remote mailbox # my $IMAP = Net::IMAP::InterIMAP::->new( %$CONF, %CONFIG{qw/quiet debug/}, 'logger-fd' => $LOGGER_FD ); + +# use BODY.PEEK[] so if something gets wrong, unpulled messages +# won't be marked as \Seen in the mailbox +my $ATTRS = join ' ', qw/ENVELOPE INTERNALDATE BODY.PEEK[]/; + +# Pull new messages from IMAP and deliver them to SMTP, then update the +# statefile +sub pull(;$) { + my $ignore = shift // []; + my @uid; + + # invariant: we're at pos 8 + 4*(1+$#ignore + 1+$#uids) in the statefile + $IMAP->pull_new_messages($ATTRS, sub($) { + my $mail = shift; + return unless exists $mail->{RFC822}; # not for us + + my $uid = $mail->{UID}; + my $from = first { defined $_ and @$_ } @{$mail->{ENVELOPE}}[2,3,4]; + $from = (defined $from and @$from) ? $from->[0]->[2].'@'.$from->[0]->[3] : ''; + print STDERR "($MAILBOX): UID $uid from <$from> ($mail->{INTERNALDATE})\n" unless $CONFIG{quiet}; + + sendmail($from, $mail->{RFC822}); + + push @uid, $uid; + writeUID($uid); + }, @$ignore); + + # now that everything has been deliverd, mark @ignore and @uid as \Seen + $IMAP->silent_store(compact_set(@$ignore, @uid), '+', '\Seen') if @$ignore or @uid; + + # update the statefile + sysseek($STATE, 4, SEEK_SET) // die "Can't seek: $!"; + my ($uidnext) = $IMAP->get_cache('UIDNEXT'); + writeUID($uidnext); + truncate($STATE, 8) // die "Can't truncate"; +} + do { my $uidvalidity = readUID(); my $uidnext = readUID(); - my @ignore; + my $ignore = []; $IMAP->set_cache($MAILBOX, UIDVALIDITY => $uidvalidity, UIDNEXT => $uidnext); $IMAP->select($MAILBOX); @@ -249,37 +286,15 @@ do { # have already been delivered, but the process exited before the # statefile was updated while (defined (my $uid = readUID())) { - push @ignore, $uid; + push @$ignore, $uid; } } - - # use BODY.PEEK[] so if something gets wrong, unpulled messages - # won't be marked as \Seen in the mailbox - my $attrs = join ' ', qw/ENVELOPE INTERNALDATE BODY.PEEK[]/; - my @uid; - - # invariant: we're at pos 8 + 4*(1+$#ignore + 1+$#uids) - $IMAP->pull_new_messages($attrs, sub($) { - my $mail = shift; - return unless exists $mail->{RFC822}; # not for us - - my $uid = $mail->{UID}; - my $from = first { defined $_ and @$_ } @{$mail->{ENVELOPE}}[2,3,4]; - $from = (defined $from and @$from) ? $from->[0]->[2].'@'.$from->[0]->[3] : ''; - print STDERR "($MAILBOX): UID $uid from <$from> ($mail->{INTERNALDATE})\n" unless $CONFIG{quiet}; - - sendmail($from, $mail->{RFC822}); - - push @uid, $uid; - writeUID($uid); - }, @ignore); - - # now that everything has been deliverd, mark @ignore and @uid as \Seen - $IMAP->silent_store(compact_set(@ignore, @uid), '+', '\Seen') if @ignore or @uid; - - # update the statefile - sysseek($STATE, 4, SEEK_SET) // die "Can't seek: $!"; - ($uidnext) = $IMAP->get_cache('UIDNEXT'); - writeUID($uidnext); - truncate($STATE, 8) // die "Can't truncate"; + pull($ignore); }; +exit 0 unless defined $CONFIG{idle}; + +$CONFIG{idle} = 1740 if defined $CONFIG{idle} and $CONFIG{idle} == 0; # 29 mins +while(1) { + my $r = $IMAP->idle($CONFIG{idle}, sub() { $IMAP->has_new_mails($MAILBOX) }); + pull() if $r; +} -- cgit v1.2.3