about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <e@yhbt.net>2020-06-10 07:05:19 +0000
committerEric Wong <e@yhbt.net>2020-06-13 07:55:45 +0000
commitc5fb8d66dfc1fa7034c6e7350bc6474cdde6b6a7 (patch)
tree655e521dc767e3728d3b8c20a7b40da94aa7b1e7
parentba5b07877f468a3d233a1bba95eabe35ba7336e1 (diff)
downloadpublic-inbox-c5fb8d66dfc1fa7034c6e7350bc6474cdde6b6a7.tar.gz
imap: FETCH: proper MSN => UID mapping for requests
This finally seems to make mutt header caching behave properly.

We expect to be able to safely load 50K IV/UVs in memory without
OOM, since that's "only" 1.6 MB that won't live beyond a single
event loop iteration.  So create a simple array which can
quickly map MSNs in requests to UIDs and not leave out messages.

MSNs in the FETCH response will NOT be correct, since it's
inefficient to implement properly and mutt doesn't seem to
care.

Since the conversion code is easily shared, "UID SEARCH" can
allow the same MSN => UID mapping non-UID "FETCH" does.
-rw-r--r--lib/PublicInbox/IMAP.pm24
1 files changed, 19 insertions, 5 deletions
diff --git a/lib/PublicInbox/IMAP.pm b/lib/PublicInbox/IMAP.pm
index 7efe5387..ff01d0b5 100644
--- a/lib/PublicInbox/IMAP.pm
+++ b/lib/PublicInbox/IMAP.pm
@@ -16,7 +16,12 @@
 #   are for the limitations of git clients, while slices are
 #   for the limitations of IMAP clients.
 #
-# * sequence numbers are estimated based on slice
+# * sequence numbers are estimated based on slice.  If they
+#   wrong, they're higher than than the corresponding UID
+#   because UIDs have gaps due to spam removals.
+#   We only support an ephemeral mapping non-UID "FETCH"
+#   because mutt header caching relies on it; mutt uses
+#   UID requests everywhere else.
 
 package PublicInbox::IMAP;
 use strict;
@@ -957,9 +962,17 @@ sub cmd_uid_fetch ($$$$;@) {
         long_response($self, $cb, $tag, [], $range_info, $ops, $partial);
 }
 
+# returns an arrayref of UIDs, so MSNs can be translated via:
+# $msn2uid->[$MSN-1] => $UID
+sub msn2uid ($) {
+        my ($self) = @_;
+        my $x = $self->{uid_base};
+        $self->{ibx}->over->uid_range($x + 1, $x + UID_SLICE);
+}
+
 sub msn_to_uid_range ($$) {
-        my $uid_base = $_[0]->{uid_base};
-        $_[1] =~ s/([0-9]+)/$uid_base + $1/sge;
+        my $msn2uid = $_[0];
+        $_[1] =~ s!([0-9]+)!$msn2uid->[$1 - 1] // ($msn2uid->[-1] + 1)!sge;
 }
 
 sub cmd_fetch ($$$$;@) {
@@ -970,7 +983,7 @@ sub cmd_fetch ($$$$;@) {
 
         # cb is one of fetch_blob, fetch_smsg, fetch_uid
         $range_csv = 'bad' if $range_csv !~ $valid_range;
-        msn_to_uid_range($self, $range_csv);
+        msn_to_uid_range(msn2uid($self), $range_csv);
         my $range_info = range_step($self, \$range_csv);
         return "$tag $range_info\r\n" if !ref($range_info);
         long_response($self, $cb, $tag, [], $range_info, $ops, $partial);
@@ -1070,13 +1083,14 @@ sub parse_query {
         my $sql = ''; # date conditions, {sql} deleted if Xapian is needed
         my $xap = '';
         my $q = { sql => \$sql, xap => \$xap };
+        my $msn2uid;
         while (@$rest) {
                 my $k = uc(shift @$rest);
                 # default criteria
                 next if $k =~ /\A(?:ALL|RECENT|UNSEEN|NEW)\z/;
                 next if $k eq 'AND'; # the default, until we support OR
                 if ($k =~ $valid_range) { # convert sequence numbers to UIDs
-                        msn_to_uid_range($self, $k);
+                        msn_to_uid_range($msn2uid //= msn2uid($self), $k);
                         push @{$q->{uid}}, $k;
                 } elsif ($k eq 'UID') {
                         $k = shift(@$rest) // '';