about summary refs log tree commit homepage
path: root/lib/PublicInbox/IMAP.pm
diff options
context:
space:
mode:
authorEric Wong <e@yhbt.net>2020-06-10 07:04:02 +0000
committerEric Wong <e@yhbt.net>2020-06-13 07:55:45 +0000
commitacb6cf984053115ed6dc5ed673786ed99e1f098d (patch)
tree5de449195d81dcec869f730c9a76ac80d6862634 /lib/PublicInbox/IMAP.pm
parent34c1a6c16733adee3acfe5861096692f3ea55378 (diff)
downloadpublic-inbox-acb6cf984053115ed6dc5ed673786ed99e1f098d.tar.gz
It seems to be working as far as Mail::IMAPClient is concerned.
Diffstat (limited to 'lib/PublicInbox/IMAP.pm')
-rw-r--r--lib/PublicInbox/IMAP.pm58
1 files changed, 55 insertions, 3 deletions
diff --git a/lib/PublicInbox/IMAP.pm b/lib/PublicInbox/IMAP.pm
index c0636066..99c6c817 100644
--- a/lib/PublicInbox/IMAP.pm
+++ b/lib/PublicInbox/IMAP.pm
@@ -15,7 +15,8 @@
 package PublicInbox::IMAP;
 use strict;
 use base qw(PublicInbox::DS);
-use fields qw(imapd logged_in ibx long_cb -login_tag);
+use fields qw(imapd logged_in ibx long_cb -login_tag
+        -idle_tag -idle_max);
 use PublicInbox::Eml;
 use PublicInbox::DS qw(now);
 use PublicInbox::Syscall qw(EPOLLIN EPOLLONESHOT);
@@ -88,7 +89,10 @@ sub new ($$$) {
 
 sub capa ($) {
         my ($self) = @_;
-        my $capa = 'CAPABILITY IMAP4rev1';
+
+        # dovecot advertises IDLE pre-login; perhaps because some clients
+        # depend on it, so we'll do the same
+        my $capa = 'CAPABILITY IMAP4rev1 IDLE';
         if ($self->{logged_in}) {
                 $capa .= ' COMPRESS=DEFLATE';
         } else {
@@ -139,6 +143,40 @@ sub cmd_capability ($$) {
 
 sub cmd_noop ($$) { "$_[1] OK NOOP completed\r\n" }
 
+# called by PublicInbox::InboxIdle
+sub on_inbox_unlock {
+        my ($self, $ibx) = @_;
+        my $new = ($ibx->mm->minmax)[1];
+        defined(my $old = $self->{-idle_max}) or die 'BUG: -idle_max unset';
+        if ($new > $old) {
+                $self->{-idle_max} = $new;
+                $self->msg_more("* $_ EXISTS\r\n") for (($old + 1)..($new - 1));
+                $self->write(\"* $new EXISTS\r\n");
+        }
+}
+
+sub cmd_idle ($$) {
+        my ($self, $tag) = @_;
+        # IDLE seems allowed by dovecot w/o a mailbox selected *shrug*
+        my $ibx = $self->{ibx} or return "$tag BAD no mailbox selected\r\n";
+        $ibx->subscribe_unlock(fileno($self->{sock}), $self);
+        $self->{-idle_tag} = $tag;
+        $self->{-idle_max} = ($ibx->mm->minmax)[1] // 0;
+        "+ idling\r\n"
+}
+
+sub cmd_done ($$) {
+        my ($self, $tag) = @_; # $tag is "DONE" (case-insensitive)
+        defined(my $idle_tag = delete $self->{-idle_tag}) or
+                return "$tag BAD not idle\r\n";
+        my $ibx = $self->{ibx} or do {
+                warn "BUG: idle_tag set w/o inbox";
+                return "$tag BAD internal bug\r\n";
+        };
+        $ibx->unsubscribe_unlock(fileno($self->{sock}));
+        "$idle_tag OK Idle completed\r\n";
+}
+
 sub cmd_examine ($$$) {
         my ($self, $tag, $mailbox) = @_;
         my $ibx = $self->{imapd}->{groups}->{$mailbox} or
@@ -361,7 +399,11 @@ sub process_line ($$) {
         }
         my $res = eval {
                 if (my $cmd = $self->can('cmd_'.lc($req // ''))) {
-                        $cmd->($self, $tag, @args);
+                        defined($self->{-idle_tag}) ?
+                                "$self->{-idle_tag} BAD expected DONE\r\n" :
+                                $cmd->($self, $tag, @args);
+                } elsif (uc($tag // '') eq 'DONE' && !defined($req)) {
+                        cmd_done($self, $tag);
                 } else { # this is weird
                         auth_challenge_ok($self) //
                                 "$tag BAD Error in IMAP command $req: ".
@@ -516,6 +558,16 @@ sub busy {
         ($self->{rbuf} || $self->{wbuf} || $self->not_idle_long($now));
 }
 
+sub close {
+        my ($self) = @_;
+        if (my $ibx = delete $self->{ibx}) {
+                if (my $sock = $self->{sock}) {;
+                        $ibx->unsubscribe_unlock(fileno($sock));
+                }
+        }
+        $self->SUPER::close; # PublicInbox::DS::close
+}
+
 # we're read-only, so SELECT and EXAMINE do the same thing
 no warnings 'once';
 *cmd_select = \&cmd_examine;