deltachat/imap/
session.rs

1use std::collections::BTreeMap;
2use std::ops::{Deref, DerefMut};
3
4use anyhow::{Context as _, Result};
5use async_imap::Session as ImapSession;
6use async_imap::types::Mailbox;
7use futures::TryStreamExt;
8
9use crate::imap::capabilities::Capabilities;
10use crate::net::session::SessionStream;
11
12/// Prefetch:
13/// - Message-ID to check if we already have the message.
14/// - In-Reply-To and References to check if message is a reply to chat message.
15/// - Chat-Version to check if a message is a chat message
16/// - Autocrypt-Setup-Message to check if a message is an autocrypt setup message,
17///   not necessarily sent by Delta Chat.
18/// - Chat-Is-Post-Message to skip it in background fetch or when it is > `DownloadLimit`.
19const PREFETCH_FLAGS: &str = "(UID INTERNALDATE RFC822.SIZE BODY.PEEK[HEADER.FIELDS (\
20                              MESSAGE-ID \
21                              DATE \
22                              X-MICROSOFT-ORIGINAL-MESSAGE-ID \
23                              FROM \
24                              CHAT-VERSION \
25                              CHAT-IS-POST-MESSAGE \
26                              AUTO-SUBMITTED \
27                              AUTOCRYPT-SETUP-MESSAGE\
28                              )])";
29
30#[derive(Debug)]
31pub(crate) struct Session {
32    transport_id: u32,
33
34    pub(super) inner: ImapSession<Box<dyn SessionStream>>,
35
36    pub capabilities: Capabilities,
37
38    /// Selected folder name.
39    pub selected_folder: Option<String>,
40
41    /// Mailbox structure returned by IMAP server.
42    pub selected_mailbox: Option<Mailbox>,
43
44    pub selected_folder_needs_expunge: bool,
45
46    /// True if currently selected folder has new messages.
47    ///
48    /// Should be false if no folder is currently selected.
49    pub new_mail: bool,
50
51    pub resync_request_sender: async_channel::Sender<()>,
52}
53
54impl Deref for Session {
55    type Target = ImapSession<Box<dyn SessionStream>>;
56
57    fn deref(&self) -> &Self::Target {
58        &self.inner
59    }
60}
61
62impl DerefMut for Session {
63    fn deref_mut(&mut self) -> &mut Self::Target {
64        &mut self.inner
65    }
66}
67
68impl Session {
69    pub(crate) fn new(
70        inner: ImapSession<Box<dyn SessionStream>>,
71        capabilities: Capabilities,
72        resync_request_sender: async_channel::Sender<()>,
73        transport_id: u32,
74    ) -> Self {
75        Self {
76            transport_id,
77            inner,
78            capabilities,
79            selected_folder: None,
80            selected_mailbox: None,
81            selected_folder_needs_expunge: false,
82            new_mail: false,
83            resync_request_sender,
84        }
85    }
86
87    /// Returns ID of the transport for which this session was created.
88    pub(crate) fn transport_id(&self) -> u32 {
89        self.transport_id
90    }
91
92    pub fn can_idle(&self) -> bool {
93        self.capabilities.can_idle
94    }
95
96    pub fn can_move(&self) -> bool {
97        self.capabilities.can_move
98    }
99
100    pub fn can_check_quota(&self) -> bool {
101        self.capabilities.can_check_quota
102    }
103
104    pub fn can_condstore(&self) -> bool {
105        self.capabilities.can_condstore
106    }
107
108    pub fn can_metadata(&self) -> bool {
109        self.capabilities.can_metadata
110    }
111
112    pub fn can_push(&self) -> bool {
113        self.capabilities.can_push
114    }
115
116    // Returns true if IMAP server has `XCHATMAIL` capability.
117    pub fn is_chatmail(&self) -> bool {
118        self.capabilities.is_chatmail
119    }
120
121    /// Returns the names of all folders on the IMAP server.
122    pub async fn list_folders(&mut self) -> Result<Vec<async_imap::types::Name>> {
123        let list = self.list(Some(""), Some("*")).await?.try_collect().await?;
124        Ok(list)
125    }
126
127    /// Prefetch `n_uids` messages starting from `uid_next`. Returns a list of fetch results in the
128    /// order of ascending delivery time to the server (INTERNALDATE).
129    #[expect(clippy::arithmetic_side_effects)]
130    pub(crate) async fn prefetch(
131        &mut self,
132        uid_next: u32,
133        n_uids: u32,
134    ) -> Result<Vec<(u32, async_imap::types::Fetch)>> {
135        let uid_last = uid_next.saturating_add(n_uids - 1);
136        // fetch messages with larger UID than the last one seen
137        let set = format!("{uid_next}:{uid_last}");
138        let mut list = self
139            .uid_fetch(set, PREFETCH_FLAGS)
140            .await
141            .context("IMAP could not fetch")?;
142
143        let mut msgs = BTreeMap::new();
144        while let Some(msg) = list.try_next().await? {
145            if let Some(msg_uid) = msg.uid {
146                msgs.insert((msg.internal_date(), msg_uid), msg);
147            }
148        }
149
150        Ok(msgs.into_iter().map(|((_, uid), msg)| (uid, msg)).collect())
151    }
152}