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