mirror of
https://github.com/chatmail/core.git
synced 2026-05-06 06:46:35 +03:00
rework select_folder error handling (thanks @flub and @link2xt for helping along)
This commit is contained in:
committed by
Floris Bruynooghe
parent
74a4691f29
commit
2dbef704e9
@@ -54,6 +54,12 @@ pub enum Error {
|
|||||||
ImapInTeardown,
|
ImapInTeardown,
|
||||||
#[fail(display = "No IMAP Connection established")]
|
#[fail(display = "No IMAP Connection established")]
|
||||||
ImapNoConnection,
|
ImapNoConnection,
|
||||||
|
/*
|
||||||
|
#[fail(display = "IMAP Connection lost: {:?}", _0)]
|
||||||
|
ImapConnectionLost(String),
|
||||||
|
#[fail(display = "Failed to obtain Imap Session")]
|
||||||
|
CouldNotObtainImapSession,
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|||||||
171
src/imap.rs
171
src/imap.rs
@@ -18,7 +18,7 @@ use async_std::task;
|
|||||||
use crate::constants::*;
|
use crate::constants::*;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::dc_receive_imf::dc_receive_imf;
|
use crate::dc_receive_imf::dc_receive_imf;
|
||||||
use crate::error::{Error, Result};
|
use crate::error::Error;
|
||||||
use crate::events::Event;
|
use crate::events::Event;
|
||||||
use crate::imap_client::*;
|
use crate::imap_client::*;
|
||||||
use crate::job::{job_add, Action};
|
use crate::job::{job_add, Action};
|
||||||
@@ -114,6 +114,24 @@ impl Default for ImapConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Fail, Clone, Eq, PartialEq)]
|
||||||
|
enum SelectError {
|
||||||
|
#[fail(display = "Could not obtain imap-session object.")]
|
||||||
|
NoSession,
|
||||||
|
|
||||||
|
#[fail(display = "Connection Lost or no connection established")]
|
||||||
|
ConnectionLost,
|
||||||
|
|
||||||
|
#[fail(display = "imap-close (to expunge messages) failed: {}", _0)]
|
||||||
|
CloseExpungeFailed(String),
|
||||||
|
|
||||||
|
#[fail(display = "Folder name invalid: {:?}", _0)]
|
||||||
|
BadFolderName(String),
|
||||||
|
|
||||||
|
#[fail(display = "async-imap select error: {:?}", _0)]
|
||||||
|
Other(String),
|
||||||
|
}
|
||||||
|
|
||||||
impl Imap {
|
impl Imap {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Imap {
|
Imap {
|
||||||
@@ -138,7 +156,7 @@ impl Imap {
|
|||||||
self.should_reconnect.store(true, Ordering::Relaxed)
|
self.should_reconnect.store(true, Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_handle_if_needed(&self, context: &Context) -> Result<()> {
|
fn setup_handle_if_needed(&self, context: &Context) -> Result<(), Error> {
|
||||||
task::block_on(async move {
|
task::block_on(async move {
|
||||||
if self.config.read().await.imap_server.is_empty() {
|
if self.config.read().await.imap_server.is_empty() {
|
||||||
return Err(Error::ImapInTeardown);
|
return Err(Error::ImapInTeardown);
|
||||||
@@ -285,7 +303,7 @@ impl Imap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Connects to imap account using already-configured parameters.
|
/// Connects to imap account using already-configured parameters.
|
||||||
pub fn connect_configured(&self, context: &Context) -> Result<()> {
|
pub fn connect_configured(&self, context: &Context) -> Result<(), Error> {
|
||||||
if async_std::task::block_on(async move {
|
if async_std::task::block_on(async move {
|
||||||
self.is_connected().await && !self.should_reconnect()
|
self.is_connected().await && !self.should_reconnect()
|
||||||
}) {
|
}) {
|
||||||
@@ -396,7 +414,7 @@ impl Imap {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch(&self, context: &Context, watch_folder: &str) -> Result<()> {
|
pub fn fetch(&self, context: &Context, watch_folder: &str) -> Result<(), Error> {
|
||||||
task::block_on(async move {
|
task::block_on(async move {
|
||||||
if !context.sql.is_open() {
|
if !context.sql.is_open() {
|
||||||
// probably shutdown
|
// probably shutdown
|
||||||
@@ -418,12 +436,12 @@ impl Imap {
|
|||||||
&self,
|
&self,
|
||||||
context: &Context,
|
context: &Context,
|
||||||
folder: Option<S>,
|
folder: Option<S>,
|
||||||
) -> ImapActionResult {
|
) -> Result<(), SelectError> {
|
||||||
if self.session.lock().await.is_none() {
|
if self.session.lock().await.is_none() {
|
||||||
let mut cfg = self.config.write().await;
|
let mut cfg = self.config.write().await;
|
||||||
cfg.selected_folder = None;
|
cfg.selected_folder = None;
|
||||||
cfg.selected_folder_needs_expunge = false;
|
cfg.selected_folder_needs_expunge = false;
|
||||||
return ImapActionResult::Failed;
|
return Err(SelectError::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 a new folder and the new folder is equal to the selected one, there's nothing to do.
|
||||||
@@ -431,7 +449,7 @@ impl Imap {
|
|||||||
if let Some(ref folder) = folder {
|
if let Some(ref folder) = folder {
|
||||||
if let Some(ref selected_folder) = self.config.read().await.selected_folder {
|
if let Some(ref selected_folder) = self.config.read().await.selected_folder {
|
||||||
if folder.as_ref() == selected_folder {
|
if folder.as_ref() == selected_folder {
|
||||||
return ImapActionResult::AlreadyDone;
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -450,12 +468,11 @@ impl Imap {
|
|||||||
info!(context, "close/expunge succeeded");
|
info!(context, "close/expunge succeeded");
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn!(context, "failed to close session: {:?}", err);
|
return Err(SelectError::CloseExpungeFailed(err.to_string()));
|
||||||
return ImapActionResult::Failed;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return ImapActionResult::Failed;
|
return Err(SelectError::NoSession);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.config.write().await.selected_folder_needs_expunge = false;
|
self.config.write().await.selected_folder_needs_expunge = false;
|
||||||
@@ -464,31 +481,39 @@ impl Imap {
|
|||||||
// select new folder
|
// select new folder
|
||||||
if let Some(ref folder) = folder {
|
if let Some(ref folder) = folder {
|
||||||
if let Some(ref mut session) = &mut *self.session.lock().await {
|
if let Some(ref mut session) = &mut *self.session.lock().await {
|
||||||
match session.select(folder).await {
|
let res = 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) => {
|
Ok(mailbox) => {
|
||||||
let mut config = self.config.write().await;
|
let mut config = self.config.write().await;
|
||||||
config.selected_folder = Some(folder.as_ref().to_string());
|
config.selected_folder = Some(folder.as_ref().to_string());
|
||||||
config.selected_mailbox = Some(mailbox);
|
config.selected_mailbox = Some(mailbox);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(async_imap::error::Error::ConnectionLost) => {
|
||||||
|
self.trigger_reconnect();
|
||||||
|
self.config.write().await.selected_folder = None;
|
||||||
|
Err(SelectError::ConnectionLost)
|
||||||
|
}
|
||||||
|
Err(async_imap::error::Error::Validate(_)) => {
|
||||||
|
Err(SelectError::BadFolderName(folder.as_ref().to_string()))
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn!(
|
|
||||||
context,
|
|
||||||
"Cannot select folder: {}; {:?}.",
|
|
||||||
folder.as_ref(),
|
|
||||||
err
|
|
||||||
);
|
|
||||||
|
|
||||||
self.config.write().await.selected_folder = None;
|
self.config.write().await.selected_folder = None;
|
||||||
self.trigger_reconnect();
|
self.trigger_reconnect();
|
||||||
return ImapActionResult::Failed;
|
Err(SelectError::Other(err.to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
unreachable!();
|
Err(SelectError::NoSession)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
ImapActionResult::Success
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_config_last_seen_uid<S: AsRef<str>>(&self, context: &Context, folder: S) -> (u32, u32) {
|
fn get_config_last_seen_uid<S: AsRef<str>>(&self, context: &Context, folder: S) -> (u32, u32) {
|
||||||
@@ -517,12 +542,13 @@ impl Imap {
|
|||||||
&self,
|
&self,
|
||||||
context: &Context,
|
context: &Context,
|
||||||
folder: S,
|
folder: S,
|
||||||
) -> Result<bool> {
|
) -> Result<bool, Error> {
|
||||||
match self.select_folder(context, Some(&folder)).await {
|
if let Err(err) = self.select_folder(context, Some(&folder)).await {
|
||||||
ImapActionResult::Failed | ImapActionResult::RetryLater => {
|
bail!(
|
||||||
bail!("Cannot select folder {:?} for fetching.", folder.as_ref());
|
"Cannot select folder {:?} for fetching: {}",
|
||||||
}
|
folder.as_ref(),
|
||||||
ImapActionResult::Success | ImapActionResult::AlreadyDone => {}
|
err
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// compare last seen UIDVALIDITY against the current one
|
// compare last seen UIDVALIDITY against the current one
|
||||||
@@ -754,7 +780,7 @@ impl Imap {
|
|||||||
1
|
1
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn idle(&self, context: &Context, watch_folder: Option<String>) -> Result<()> {
|
pub fn idle(&self, context: &Context, watch_folder: Option<String>) -> Result<(), Error> {
|
||||||
task::block_on(async move {
|
task::block_on(async move {
|
||||||
if !self.config.read().await.can_idle {
|
if !self.config.read().await.can_idle {
|
||||||
return Err(Error::ImapMissesIdle);
|
return Err(Error::ImapMissesIdle);
|
||||||
@@ -762,12 +788,11 @@ impl Imap {
|
|||||||
|
|
||||||
self.setup_handle_if_needed(context)?;
|
self.setup_handle_if_needed(context)?;
|
||||||
|
|
||||||
match self.select_folder(context, watch_folder.clone()).await {
|
if let Err(err) = self.select_folder(context, watch_folder.clone()).await {
|
||||||
ImapActionResult::Success | ImapActionResult::AlreadyDone => {}
|
return Err(Error::ImapSelectFailed(format!(
|
||||||
|
"{:?}: {:?}",
|
||||||
ImapActionResult::Failed | ImapActionResult::RetryLater => {
|
watch_folder, err
|
||||||
return Err(Error::ImapSelectFailed(format!("{:?}", watch_folder)));
|
)));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let session = self.session.lock().await.take();
|
let session = self.session.lock().await.take();
|
||||||
@@ -1097,14 +1122,22 @@ impl Imap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
match self.select_folder(context, Some(&folder)).await {
|
match self.select_folder(context, Some(&folder)).await {
|
||||||
ImapActionResult::Success | ImapActionResult::AlreadyDone => None,
|
Ok(()) => None,
|
||||||
res => {
|
Err(SelectError::ConnectionLost) => {
|
||||||
warn!(
|
warn!(context, "Lost imap connection");
|
||||||
context,
|
Some(ImapActionResult::RetryLater)
|
||||||
"Cannot select folder {} for preparing IMAP operation", folder
|
}
|
||||||
);
|
Err(SelectError::NoSession) => {
|
||||||
|
warn!(context, "no imap session");
|
||||||
Some(res)
|
Some(ImapActionResult::Failed)
|
||||||
|
}
|
||||||
|
Err(SelectError::BadFolderName(folder_name)) => {
|
||||||
|
warn!(context, "invalid folder name: {:?}", folder_name);
|
||||||
|
Some(ImapActionResult::Failed)
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
warn!(context, "failed to select folder: {:?}: {:?}", folder, err);
|
||||||
|
Some(ImapActionResult::RetryLater)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -1330,33 +1363,35 @@ impl Imap {
|
|||||||
info!(context, "emptying folder {}", folder);
|
info!(context, "emptying folder {}", folder);
|
||||||
|
|
||||||
if folder.is_empty() {
|
if folder.is_empty() {
|
||||||
warn!(context, "cannot perform empty, folder not set");
|
error!(context, "cannot perform empty, folder not set");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
match self.select_folder(context, Some(&folder)).await {
|
if let Err(err) = self.select_folder(context, Some(&folder)).await {
|
||||||
ImapActionResult::Success | ImapActionResult::AlreadyDone => {
|
// we want to report all error to the user
|
||||||
if !self
|
// (no retry should be attempted)
|
||||||
.add_flag_finalized_with_set(context, SELECT_ALL, "\\Deleted")
|
error!(
|
||||||
.await
|
context,
|
||||||
{
|
"Could not select {} for expunging: {:?}", folder, err
|
||||||
warn!(context, "Cannot empty folder {}", folder);
|
);
|
||||||
} else {
|
return;
|
||||||
// we now trigger expunge to actually delete messages
|
}
|
||||||
self.config.write().await.selected_folder_needs_expunge = true;
|
|
||||||
if self.select_folder::<String>(context, None).await
|
if !self
|
||||||
== ImapActionResult::Success
|
.add_flag_finalized_with_set(context, SELECT_ALL, "\\Deleted")
|
||||||
{
|
.await
|
||||||
emit_event!(context, Event::ImapFolderEmptied(folder.to_string()));
|
{
|
||||||
} else {
|
error!(context, "Cannot mark messages for deletion {}", folder);
|
||||||
warn!(
|
return;
|
||||||
context,
|
}
|
||||||
"could not perform expunge on empty-marked folder {}", folder
|
|
||||||
);
|
// we now trigger expunge to actually delete messages
|
||||||
}
|
self.config.write().await.selected_folder_needs_expunge = true;
|
||||||
}
|
match self.select_folder::<String>(context, None).await {
|
||||||
|
Ok(()) => {
|
||||||
|
emit_event!(context, Event::ImapFolderEmptied(folder.to_string()));
|
||||||
}
|
}
|
||||||
ImapActionResult::Failed | ImapActionResult::RetryLater => {
|
Err(err) => {
|
||||||
warn!(context, "could not select folder {}", folder);
|
error!(context, "expunge failed {}: {:?}", folder, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1434,7 +1469,7 @@ fn precheck_imf(context: &Context, rfc724_mid: &str, server_folder: &str, server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prefetch_get_message_id(prefetch_msg: &Fetch) -> Result<String> {
|
fn prefetch_get_message_id(prefetch_msg: &Fetch) -> Result<String, Error> {
|
||||||
ensure!(
|
ensure!(
|
||||||
prefetch_msg.envelope().is_some(),
|
prefetch_msg.envelope().is_some(),
|
||||||
"Fetched message has no envelope"
|
"Fetched message has no envelope"
|
||||||
|
|||||||
Reference in New Issue
Block a user