deltachat/imap/
select_folder.rs1use anyhow::Context as _;
4
5use super::session::Session as ImapSession;
6use super::{get_uid_next, get_uidvalidity, set_modseq, set_uid_next, set_uidvalidity};
7use crate::context::Context;
8use crate::log::{info, warn};
9
10type Result<T> = std::result::Result<T, Error>;
11
12#[derive(Debug, thiserror::Error)]
13pub enum Error {
14 #[error("Got a NO response when trying to select {0}, usually this means that it doesn't exist: {1}")]
15 NoFolder(String, String),
16
17 #[error("IMAP other error: {0}")]
18 Other(String),
19}
20
21impl From<anyhow::Error> for Error {
22 fn from(err: anyhow::Error) -> Error {
23 Error::Other(format!("{err:#}"))
24 }
25}
26
27impl ImapSession {
28 pub(super) async fn maybe_close_folder(&mut self, context: &Context) -> anyhow::Result<()> {
35 if let Some(folder) = &self.selected_folder {
36 if self.selected_folder_needs_expunge {
37 info!(context, "Expunge messages in {folder:?}.");
38
39 self.close().await.context("IMAP close/expunge failed")?;
40 info!(context, "Close/expunge succeeded.");
41 self.selected_folder = None;
42 self.selected_folder_needs_expunge = false;
43 self.new_mail = false;
44 }
45 }
46 Ok(())
47 }
48
49 async fn select_folder(&mut self, context: &Context, folder: &str) -> Result<NewlySelected> {
53 if let Some(selected_folder) = &self.selected_folder {
56 if folder == selected_folder {
57 return Ok(NewlySelected::No);
58 }
59 }
60
61 self.maybe_close_folder(context).await?;
63
64 let res = if self.can_condstore() {
66 self.select_condstore(folder).await
67 } else {
68 self.select(folder).await
69 };
70
71 match res {
76 Ok(mailbox) => {
77 info!(context, "Selected folder {folder:?}.");
78 self.selected_folder = Some(folder.to_string());
79 self.selected_mailbox = Some(mailbox);
80 Ok(NewlySelected::Yes)
81 }
82 Err(async_imap::error::Error::No(response)) => {
83 Err(Error::NoFolder(folder.to_string(), response))
84 }
85 Err(err) => Err(Error::Other(err.to_string())),
86 }
87 }
88
89 pub(super) async fn select_or_create_folder(
91 &mut self,
92 context: &Context,
93 folder: &str,
94 ) -> anyhow::Result<NewlySelected> {
95 match self.select_folder(context, folder).await {
96 Ok(newly_selected) => Ok(newly_selected),
97 Err(err) => match err {
98 Error::NoFolder(..) => {
99 info!(context, "Failed to select folder {folder:?} because it does not exist, trying to create it.");
100 let create_res = self.create(folder).await;
101 if let Err(ref err) = create_res {
102 info!(context, "Couldn't select folder, then create() failed: {err:#}.");
103 }
105 let select_res = self.select_folder(context, folder).await.with_context(|| format!("failed to select newely created folder {folder}"));
106 if select_res.is_err() {
107 create_res?;
108 }
109 select_res
110 }
111 _ => Err(err).with_context(|| format!("failed to select folder {folder} with error other than NO, not trying to create it")),
112 },
113 }
114 }
115
116 pub(crate) async fn select_with_uidvalidity(
126 &mut self,
127 context: &Context,
128 folder: &str,
129 create: bool,
130 ) -> Result<bool> {
131 let newly_selected = if create {
132 self.select_or_create_folder(context, folder)
133 .await
134 .with_context(|| format!("Failed to select or create folder {folder:?}"))?
135 } else {
136 match self.select_folder(context, folder).await {
137 Ok(newly_selected) => newly_selected,
138 Err(err) => match err {
139 Error::NoFolder(..) => return Ok(false),
140 _ => {
141 return Err(err)
142 .with_context(|| format!("Failed to select folder {folder:?}"))?
143 }
144 },
145 }
146 };
147 let mailbox = self
148 .selected_mailbox
149 .as_mut()
150 .with_context(|| format!("No mailbox selected, folder: {folder:?}"))?;
151
152 let old_uid_validity = get_uidvalidity(context, folder)
153 .await
154 .with_context(|| format!("Failed to get old UID validity for folder {folder:?}"))?;
155 let old_uid_next = get_uid_next(context, folder)
156 .await
157 .with_context(|| format!("Failed to get old UID NEXT for folder {folder:?}"))?;
158
159 let new_uid_validity = mailbox
160 .uid_validity
161 .with_context(|| format!("No UIDVALIDITY for folder {folder}"))?;
162 let new_uid_next = if let Some(uid_next) = mailbox.uid_next {
163 Some(uid_next)
164 } else {
165 warn!(
166 context,
167 "SELECT response for IMAP folder {folder:?} has no UIDNEXT, fall back to STATUS command."
168 );
169
170 let status = self
183 .inner
184 .status(folder, "(UIDNEXT)")
185 .await
186 .with_context(|| format!("STATUS (UIDNEXT) error for {folder:?}"))?;
187
188 if status.uid_next.is_none() {
189 warn!(context, "STATUS {folder} (UIDNEXT) did not return UIDNEXT.");
193 }
194 status.uid_next
195 };
196 mailbox.uid_next = new_uid_next;
197
198 if new_uid_validity == old_uid_validity {
199 if newly_selected == NewlySelected::Yes {
200 if let Some(new_uid_next) = new_uid_next {
201 if new_uid_next < old_uid_next {
202 warn!(
203 context,
204 "The server illegally decreased the uid_next of folder {folder:?} from {old_uid_next} to {new_uid_next} without changing validity ({new_uid_validity}), resyncing UIDs...",
205 );
206 set_uid_next(context, folder, new_uid_next).await?;
207 context.schedule_resync().await?;
208 }
209
210 self.new_mail |= new_uid_next != old_uid_next;
212 } else {
213 warn!(context, "Folder {folder} was just selected but we failed to determine UIDNEXT, assume that it has new mail.");
214 self.new_mail = true;
215 }
216 }
217
218 return Ok(true);
219 }
220
221 set_modseq(context, folder, 0).await?;
223
224 let new_uid_next = new_uid_next.unwrap_or_default();
227 set_uid_next(context, folder, new_uid_next).await?;
228 set_uidvalidity(context, folder, new_uid_validity).await?;
229 self.new_mail = true;
230
231 context
233 .sql
234 .execute(
235 "DELETE FROM imap WHERE folder=? AND uidvalidity!=?",
236 (&folder, new_uid_validity),
237 )
238 .await?;
239
240 if old_uid_validity != 0 || old_uid_next != 0 {
241 context.schedule_resync().await?;
242 }
243 info!(
244 context,
245 "uid/validity change folder {}: new {}/{} previous {}/{}.",
246 folder,
247 new_uid_next,
248 new_uid_validity,
249 old_uid_next,
250 old_uid_validity,
251 );
252 Ok(true)
253 }
254}
255
256#[derive(PartialEq, Debug, Copy, Clone, Eq)]
257pub(crate) enum NewlySelected {
258 Yes,
260 No,
263}