mirror of
https://github.com/chatmail/core.git
synced 2026-04-23 00:16:34 +03:00
Move IMAP session state into imap::session::Session
IMAP capabilities and selected folder are IMAP session, not IMAP client property. Moving most operations into IMAP session structure removes the need to constantly check whether IMAP session exists and reduces number of invalid states, e.g. when a folder is selected but there is no connection. Capabilities are determined immediately after logging in, so there is no need for `capabilities_determined` flag anymore. Capabilities of the server are always known if there is a session. `should_reconnect` flag and `disconnect()` function are removed: we drop the session on error. Even though RFC 3501 says that a client SHOULD NOT close the connection without a LOGOUT, it is more reliable to always just drop the connection, especially after an error.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
use super::Imap;
|
||||
use super::session::Session as ImapSession;
|
||||
|
||||
use crate::context::Context;
|
||||
use anyhow::Context as _;
|
||||
@@ -7,9 +7,6 @@ type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("IMAP Could not obtain imap-session object.")]
|
||||
NoSession,
|
||||
|
||||
#[error("IMAP Connection Lost or no connection established")]
|
||||
ConnectionLost,
|
||||
|
||||
@@ -29,55 +26,38 @@ impl From<anyhow::Error> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl Imap {
|
||||
/// Issues a CLOSE command to expunge selected folder.
|
||||
impl ImapSession {
|
||||
/// Issues a CLOSE command if selected folder needs expunge,
|
||||
/// i.e. if Delta Chat marked a message there as deleted previously.
|
||||
///
|
||||
/// CLOSE is considerably faster than an EXPUNGE, see
|
||||
/// <https://tools.ietf.org/html/rfc3501#section-6.4.2>
|
||||
pub(super) async fn close_folder(&mut self, context: &Context) -> anyhow::Result<()> {
|
||||
if let Some(ref folder) = self.config.selected_folder {
|
||||
info!(context, "Expunge messages in \"{}\".", folder);
|
||||
pub(super) async fn maybe_close_folder(&mut self, context: &Context) -> anyhow::Result<()> {
|
||||
if let Some(folder) = &self.selected_folder {
|
||||
if self.selected_folder_needs_expunge {
|
||||
info!(context, "Expunge messages in \"{}\".", folder);
|
||||
|
||||
let session = self.session.as_mut().context("no session")?;
|
||||
if let Err(err) = session.close().await.context("IMAP close/expunge failed") {
|
||||
self.trigger_reconnect(context).await;
|
||||
return Err(err);
|
||||
self.close().await.context("IMAP close/expunge failed")?;
|
||||
info!(context, "close/expunge succeeded");
|
||||
self.selected_folder = None;
|
||||
self.selected_folder_needs_expunge = false;
|
||||
}
|
||||
info!(context, "close/expunge succeeded");
|
||||
}
|
||||
self.config.selected_folder = None;
|
||||
self.config.selected_folder_needs_expunge = false;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Issues a CLOSE command if selected folder needs expunge.
|
||||
pub(crate) async fn maybe_close_folder(&mut self, context: &Context) -> anyhow::Result<()> {
|
||||
if self.config.selected_folder_needs_expunge {
|
||||
self.close_folder(context).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// select a folder, possibly update uid_validity and, if needed,
|
||||
/// expunge the folder to remove delete-marked messages.
|
||||
/// Selects a folder, possibly updating uid_validity and, if needed,
|
||||
/// expunging the folder to remove delete-marked messages.
|
||||
/// Returns whether a new folder was selected.
|
||||
pub(super) async fn select_folder(
|
||||
&mut self,
|
||||
context: &Context,
|
||||
folder: Option<&str>,
|
||||
) -> Result<NewlySelected> {
|
||||
if self.session.is_none() {
|
||||
self.config.selected_folder = None;
|
||||
self.config.selected_folder_needs_expunge = false;
|
||||
self.trigger_reconnect(context).await;
|
||||
return Err(Error::NoSession);
|
||||
}
|
||||
|
||||
// if there is a new folder and the new folder is equal to the selected one, there's nothing to do.
|
||||
// if there is _no_ new folder, we continue as we might want to expunge below.
|
||||
if let Some(folder) = folder {
|
||||
if let Some(ref selected_folder) = self.config.selected_folder {
|
||||
if let Some(selected_folder) = &self.selected_folder {
|
||||
if folder == selected_folder {
|
||||
return Ok(NewlySelected::No);
|
||||
}
|
||||
@@ -89,42 +69,30 @@ impl Imap {
|
||||
|
||||
// select new folder
|
||||
if let Some(folder) = folder {
|
||||
if let Some(ref mut session) = &mut self.session {
|
||||
let res = if self.config.can_condstore {
|
||||
session.select_condstore(folder).await
|
||||
} else {
|
||||
session.select(folder).await
|
||||
};
|
||||
|
||||
// <https://tools.ietf.org/html/rfc3501#section-6.3.1>
|
||||
// says that if the server reports select failure we are in
|
||||
// authenticated (not-select) state.
|
||||
|
||||
match res {
|
||||
Ok(mailbox) => {
|
||||
self.config.selected_folder = Some(folder.to_string());
|
||||
self.config.selected_mailbox = Some(mailbox);
|
||||
Ok(NewlySelected::Yes)
|
||||
}
|
||||
Err(async_imap::error::Error::ConnectionLost) => {
|
||||
self.trigger_reconnect(context).await;
|
||||
self.config.selected_folder = None;
|
||||
Err(Error::ConnectionLost)
|
||||
}
|
||||
Err(async_imap::error::Error::Validate(_)) => {
|
||||
Err(Error::BadFolderName(folder.to_string()))
|
||||
}
|
||||
Err(async_imap::error::Error::No(response)) => {
|
||||
Err(Error::NoFolder(folder.to_string(), response))
|
||||
}
|
||||
Err(err) => {
|
||||
self.config.selected_folder = None;
|
||||
self.trigger_reconnect(context).await;
|
||||
Err(Error::Other(err.to_string()))
|
||||
}
|
||||
}
|
||||
let res = if self.can_condstore() {
|
||||
self.select_condstore(folder).await
|
||||
} else {
|
||||
Err(Error::NoSession)
|
||||
self.select(folder).await
|
||||
};
|
||||
|
||||
// <https://tools.ietf.org/html/rfc3501#section-6.3.1>
|
||||
// says that if the server reports select failure we are in
|
||||
// authenticated (not-select) state.
|
||||
|
||||
match res {
|
||||
Ok(mailbox) => {
|
||||
self.selected_folder = Some(folder.to_string());
|
||||
self.selected_mailbox = Some(mailbox);
|
||||
Ok(NewlySelected::Yes)
|
||||
}
|
||||
Err(async_imap::error::Error::ConnectionLost) => Err(Error::ConnectionLost),
|
||||
Err(async_imap::error::Error::Validate(_)) => {
|
||||
Err(Error::BadFolderName(folder.to_string()))
|
||||
}
|
||||
Err(async_imap::error::Error::No(response)) => {
|
||||
Err(Error::NoFolder(folder.to_string(), response))
|
||||
}
|
||||
Err(err) => Err(Error::Other(err.to_string())),
|
||||
}
|
||||
} else {
|
||||
Ok(NewlySelected::No)
|
||||
@@ -141,8 +109,7 @@ impl Imap {
|
||||
Ok(newly_selected) => Ok(newly_selected),
|
||||
Err(err) => match err {
|
||||
Error::NoFolder(..) => {
|
||||
let session = self.session.as_mut().context("no IMAP session")?;
|
||||
session.create(folder).await.with_context(|| {
|
||||
self.create(folder).await.with_context(|| {
|
||||
format!("Couldn't select folder ('{}'), then create() failed", err)
|
||||
})?;
|
||||
|
||||
@@ -153,6 +120,7 @@ impl Imap {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Copy, Clone, Eq)]
|
||||
pub(super) enum NewlySelected {
|
||||
/// The folder was newly selected during this call to select_folder().
|
||||
|
||||
Reference in New Issue
Block a user