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