# as a 50K uint16_t array (via pack("S*", ...)). "UID offset"
# is the offset from {uid_base} which determines the start of
# the mailbox slice.
-
+#
+# fields:
+# imapd: PublicInbox::IMAPD ref
+# ibx: PublicInbox::Inbox ref
+# long_cb: long_response private data
+# uid_base: base UID for mailbox slice (0-based)
+# -login_tag: IMAP TAG for LOGIN
+# -idle_tag: IMAP response tag for IDLE
+# uo2m: UID-to-MSN mapping
package PublicInbox::IMAP;
use strict;
-use base qw(PublicInbox::DS);
-use fields qw(imapd ibx long_cb -login_tag
- uid_base -idle_tag uo2m);
+use parent qw(PublicInbox::DS);
use PublicInbox::Eml;
use PublicInbox::EmlContentFoo qw(parse_content_disposition);
use PublicInbox::DS qw(now);
use PublicInbox::GitAsyncCat;
use Text::ParseWords qw(parse_line);
use Errno qw(EAGAIN);
-use Hash::Util qw(unlock_hash); # dependency of fields for perl 5.10+, anyways
-use PublicInbox::Search;
use PublicInbox::IMAPsearchqp;
-*mdocid = \&PublicInbox::Search::mdocid;
my $Address;
for my $mod (qw(Email::Address::XS Mail::Address)) {
sub new ($$$) {
my ($class, $sock, $imapd) = @_;
- my $self = fields::new('PublicInbox::IMAP_preauth');
- unlock_hash(%$self);
+ my $self = bless { imapd => $imapd }, 'PublicInbox::IMAP_preauth';
my $ev = EPOLLIN;
my $wbuf;
if ($sock->can('accept_SSL') && !$sock->accept_SSL) {
$wbuf = [ \&PublicInbox::DS::accept_tls_step, \&greet ];
}
$self->SUPER::new($sock, $ev | EPOLLONESHOT);
- $self->{imapd} = $imapd;
if ($wbuf) {
$self->{wbuf} = $wbuf;
} else {
# uo2m: UID Offset to MSN, this is an arrayref by default,
# but uo2m_hibernate can compact and deduplicate it
-sub uo2m_ary_new ($) {
- my ($self) = @_;
+sub uo2m_ary_new ($;$) {
+ my ($self, $exists) = @_;
my $base = $self->{uid_base};
my $uids = $self->{ibx}->over->uid_range($base + 1, $base + UID_SLICE);
my $msn = 0;
++$base;
$tmp[$_ - $base] = ++$msn for @$uids;
+ $$exists = $msn if $exists;
\@tmp;
}
my $base = $self->{uid_base};
++$beg;
my $uids = $self->{ibx}->over->uid_range($beg, $base + UID_SLICE);
+ return $uo2m if !scalar(@$uids);
my @tmp; # [$UID_OFFSET] => $MSN
my $write_method = $_[2] // 'msg_more';
if (ref($uo2m)) {
push @$l, map { qq[* LIST (\\HasNoChildren) "." $_\r\n] } @created;
}
-sub inbox_lookup ($$) {
- my ($self, $mailbox) = @_;
- my ($ibx, $exists, $uidnext, $uid_base);
- if ($mailbox =~ /\A(.+)\.([0-9]+)\z/) {
- # old mail: inbox.comp.foo.$SLICE_IDX
- my $mb_top = $1;
- $uid_base = $2 * UID_SLICE;
- $ibx = $self->{imapd}->{mailboxes}->{lc $mailbox} or return;
- my $max;
- ($exists, $uidnext, $max) = $ibx->over->imap_status($uid_base,
- $uid_base + UID_SLICE);
- ensure_slices_exist($self->{imapd}, $ibx, $max);
- } else { # check for dummy inboxes
- $mailbox = lc $mailbox;
- $ibx = $self->{imapd}->{mailboxes}->{$mailbox} or return;
-
+sub inbox_lookup ($$;$) {
+ my ($self, $mailbox, $examine) = @_;
+ my ($ibx, $exists, $uidmax, $uid_base) = (undef, 0, 0, 0);
+ $mailbox = lc $mailbox;
+ $ibx = $self->{imapd}->{mailboxes}->{$mailbox} or return;
+ my $over = $ibx->over;
+ if ($over != $ibx) { # not a dummy
+ $mailbox =~ /\.([0-9]+)\z/ or
+ die "BUG: unexpected dummy mailbox: $mailbox\n";
+ $uid_base = $1 * UID_SLICE;
+
+ # ->num_highwater caches for writers, so use ->meta_accessor
+ $uidmax = $ibx->mm->meta_accessor('num_highwater') // 0;
+ if ($examine) {
+ $self->{uid_base} = $uid_base;
+ $self->{ibx} = $ibx;
+ $self->{uo2m} = uo2m_ary_new($self, \$exists);
+ } else {
+ $exists = $over->imap_exists;
+ }
+ ensure_slices_exist($self->{imapd}, $ibx, $over->max);
+ } else {
+ if ($examine) {
+ $self->{uid_base} = $uid_base;
+ $self->{ibx} = $ibx;
+ delete $self->{uo2m};
+ }
# if "INBOX.foo.bar" is selected and "INBOX.foo.bar.0",
# check for new UID ranges (e.g. "INBOX.foo.bar.1")
if (my $z = $self->{imapd}->{mailboxes}->{"$mailbox.0"}) {
ensure_slices_exist($self->{imapd}, $z, $z->over->max);
}
-
- $uid_base = $exists = 0;
- $uidnext = 1;
}
- ($ibx, $exists, $uidnext, $uid_base);
+ ($ibx, $exists, $uidmax + 1, $uid_base);
}
sub cmd_examine ($$$) {
my ($self, $tag, $mailbox) = @_;
- my ($ibx, $exists, $uidnext, $base) = inbox_lookup($self, $mailbox);
- return "$tag NO Mailbox doesn't exist: $mailbox\r\n" if !$ibx;
- $self->{uid_base} = $base;
- delete $self->{uo2m};
-
# XXX: do we need this? RFC 5162/7162
my $ret = $self->{ibx} ? "* OK [CLOSED] previous closed\r\n" : '';
- $self->{ibx} = $ibx;
+ my ($ibx, $exists, $uidnext, $base) = inbox_lookup($self, $mailbox, 1);
+ return "$tag NO Mailbox doesn't exist: $mailbox\r\n" if !$ibx;
$ret .= <<EOF;
* $exists EXISTS\r
* $exists RECENT\r
if (!defined($oid)) {
# it's possible to have TOCTOU if an admin runs
# public-inbox-(edit|purge), just move onto the next message
+ warn "E: $smsg->{blob} missing in $self->{ibx}->{inboxdir}\n";
return requeue_once($self);
} else {
$smsg->{blob} eq $oid or die "BUG: $smsg->{blob} != $oid";
}
+ my $pre;
+ if (!$self->{wbuf} && (my $nxt = $msgs->[0])) {
+ $pre = $self->{ibx}->git->async_prefetch($nxt->{blob},
+ \&fetch_blob_cb, $fetch_arg);
+ }
fetch_run_ops($self, $smsg, $bref, $ops, $partial);
- requeue_once($self);
+ $pre ? $self->zflush : requeue_once($self);
}
sub emit_rfc822 {
my ($beg, $end) = @$range_info;
my $srch = $self->{ibx}->search;
my $opt = { mset => 2, limit => 1000 };
- my $nshard = $srch->{nshard} // 1;
my $mset = $srch->query("$q uid:$beg..$end", $opt);
- @$uids = map { mdocid($nshard, $_) } $mset->items;
+ @$uids = @{$srch->mset_to_artnums($mset)};
if (@$uids) {
$range_info->[0] = $uids->[-1] + 1; # update $beg
return; # possibly more