refactor(imap): move prefetch() to Session

This commit is contained in:
link2xt
2024-02-28 21:52:24 +00:00
parent d2e86c5852
commit f0091696c2
2 changed files with 61 additions and 56 deletions

View File

@@ -4,7 +4,6 @@
//! to implement connect, fetch, delete functionality with standard IMAP servers. //! to implement connect, fetch, delete functionality with standard IMAP servers.
use std::{ use std::{
cmp,
cmp::max, cmp::max,
collections::{BTreeMap, BTreeSet, HashMap}, collections::{BTreeMap, BTreeSet, HashMap},
iter::Peekable, iter::Peekable,
@@ -22,7 +21,7 @@ use tokio::sync::RwLock;
use crate::chat::{self, ChatId, ChatIdBlocked}; use crate::chat::{self, ChatId, ChatIdBlocked};
use crate::config::Config; use crate::config::Config;
use crate::constants::{self, Blocked, Chattype, ShowEmails, DC_FETCH_EXISTING_MSGS_COUNT}; use crate::constants::{self, Blocked, Chattype, ShowEmails};
use crate::contact::{normalize_name, Contact, ContactAddress, ContactId, Modifier, Origin}; use crate::contact::{normalize_name, Contact, ContactAddress, ContactId, Modifier, Origin};
use crate::context::Context; use crate::context::Context;
use crate::events::EventType; use crate::events::EventType;
@@ -63,21 +62,6 @@ pub enum ImapActionResult {
Success, Success,
} }
/// Prefetch:
/// - Message-ID to check if we already have the message.
/// - In-Reply-To and References to check if message is a reply to chat message.
/// - Chat-Version to check if a message is a chat message
/// - Autocrypt-Setup-Message to check if a message is an autocrypt setup message,
/// not necessarily sent by Delta Chat.
const PREFETCH_FLAGS: &str = "(UID INTERNALDATE RFC822.SIZE BODY.PEEK[HEADER.FIELDS (\
MESSAGE-ID \
DATE \
X-MICROSOFT-ORIGINAL-MESSAGE-ID \
FROM \
IN-REPLY-TO REFERENCES \
CHAT-VERSION \
AUTOCRYPT-SETUP-MESSAGE\
)])";
const RFC724MID_UID: &str = "(UID BODY.PEEK[HEADER.FIELDS (\ const RFC724MID_UID: &str = "(UID BODY.PEEK[HEADER.FIELDS (\
MESSAGE-ID \ MESSAGE-ID \
X-MICROSOFT-ORIGINAL-MESSAGE-ID\ X-MICROSOFT-ORIGINAL-MESSAGE-ID\
@@ -758,12 +742,14 @@ impl Imap {
let uid_validity = get_uidvalidity(context, folder).await?; let uid_validity = get_uidvalidity(context, folder).await?;
let old_uid_next = get_uid_next(context, folder).await?; let old_uid_next = get_uid_next(context, folder).await?;
let session = self.session.as_mut().context("No IMAP session")?;
let msgs = if fetch_existing_msgs { let msgs = if fetch_existing_msgs {
self.prefetch_existing_msgs() session
.prefetch_existing_msgs()
.await .await
.context("prefetch_existing_msgs")? .context("prefetch_existing_msgs")?
} else { } else {
self.prefetch(old_uid_next).await.context("prefetch")? session.prefetch(old_uid_next).await.context("prefetch")?
}; };
let read_cnt = msgs.len(); let read_cnt = msgs.len();
@@ -1386,40 +1372,6 @@ impl Imap {
Ok(result) Ok(result)
} }
/// Prefetch all messages greater than or equal to `uid_next`. Returns a list of fetch results
/// in the order of ascending delivery time to the server (INTERNALDATE).
async fn prefetch(&mut self, uid_next: u32) -> Result<Vec<(u32, async_imap::types::Fetch)>> {
let session = self
.session
.as_mut()
.context("no IMAP connection established")?;
// fetch messages with larger UID than the last one seen
let set = format!("{uid_next}:*");
let mut list = session
.uid_fetch(set, PREFETCH_FLAGS)
.await
.context("IMAP could not fetch")?;
let mut msgs = BTreeMap::new();
while let Some(msg) = list.try_next().await? {
if let Some(msg_uid) = msg.uid {
// If the mailbox is not empty, results always include
// at least one UID, even if last_seen_uid+1 is past
// the last UID in the mailbox. It happens because
// uid:* is interpreted the same way as *:uid.
// See <https://tools.ietf.org/html/rfc3501#page-61> for
// standard reference. Therefore, sometimes we receive
// already seen messages and have to filter them out.
if msg_uid >= uid_next {
msgs.insert((msg.internal_date(), msg_uid), msg);
}
}
}
Ok(msgs.into_iter().map(|((_, uid), msg)| (uid, msg)).collect())
}
/// Fetches a list of messages by server UID. /// Fetches a list of messages by server UID.
/// ///
/// Returns the last UID fetched successfully and the info about each downloaded message. /// Returns the last UID fetched successfully and the info about each downloaded message.

View File

@@ -1,13 +1,32 @@
use std::cmp;
use std::collections::BTreeMap;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use anyhow::Result; use anyhow::{Context as _, Result};
use async_imap::types::Mailbox; use async_imap::types::Mailbox;
use async_imap::Session as ImapSession; use async_imap::Session as ImapSession;
use futures::TryStreamExt; use futures::TryStreamExt;
use crate::constants::DC_FETCH_EXISTING_MSGS_COUNT;
use crate::imap::capabilities::Capabilities; use crate::imap::capabilities::Capabilities;
use crate::net::session::SessionStream; use crate::net::session::SessionStream;
/// Prefetch:
/// - Message-ID to check if we already have the message.
/// - In-Reply-To and References to check if message is a reply to chat message.
/// - Chat-Version to check if a message is a chat message
/// - Autocrypt-Setup-Message to check if a message is an autocrypt setup message,
/// not necessarily sent by Delta Chat.
const PREFETCH_FLAGS: &str = "(UID INTERNALDATE RFC822.SIZE BODY.PEEK[HEADER.FIELDS (\
MESSAGE-ID \
DATE \
X-MICROSOFT-ORIGINAL-MESSAGE-ID \
FROM \
IN-REPLY-TO REFERENCES \
CHAT-VERSION \
AUTOCRYPT-SETUP-MESSAGE\
)])";
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct Session { pub(crate) struct Session {
pub(super) inner: ImapSession<Box<dyn SessionStream>>, pub(super) inner: ImapSession<Box<dyn SessionStream>>,
@@ -77,8 +96,42 @@ impl Session {
Ok(list) Ok(list)
} }
/// Like fetch_after(), but not for new messages but existing ones (the DC_FETCH_EXISTING_MSGS_COUNT newest messages) /// Prefetch all messages greater than or equal to `uid_next`. Returns a list of fetch results
async fn prefetch_existing_msgs(&mut self) -> Result<Vec<(u32, async_imap::types::Fetch)>> { /// in the order of ascending delivery time to the server (INTERNALDATE).
pub(crate) async fn prefetch(
&mut self,
uid_next: u32,
) -> Result<Vec<(u32, async_imap::types::Fetch)>> {
// fetch messages with larger UID than the last one seen
let set = format!("{uid_next}:*");
let mut list = self
.uid_fetch(set, PREFETCH_FLAGS)
.await
.context("IMAP could not fetch")?;
let mut msgs = BTreeMap::new();
while let Some(msg) = list.try_next().await? {
if let Some(msg_uid) = msg.uid {
// If the mailbox is not empty, results always include
// at least one UID, even if last_seen_uid+1 is past
// the last UID in the mailbox. It happens because
// uid:* is interpreted the same way as *:uid.
// See <https://tools.ietf.org/html/rfc3501#page-61> for
// standard reference. Therefore, sometimes we receive
// already seen messages and have to filter them out.
if msg_uid >= uid_next {
msgs.insert((msg.internal_date(), msg_uid), msg);
}
}
}
Ok(msgs.into_iter().map(|((_, uid), msg)| (uid, msg)).collect())
}
/// Like prefetch(), but not for new messages but existing ones (the DC_FETCH_EXISTING_MSGS_COUNT newest messages)
pub(crate) async fn prefetch_existing_msgs(
&mut self,
) -> Result<Vec<(u32, async_imap::types::Fetch)>> {
let exists: i64 = { let exists: i64 = {
let mailbox = self.selected_mailbox.as_ref().context("no mailbox")?; let mailbox = self.selected_mailbox.as_ref().context("no mailbox")?;
mailbox.exists.into() mailbox.exists.into()