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