move imap errors into imap module

This commit is contained in:
holger krekel
2019-12-01 00:53:02 +01:00
parent 603d55114b
commit c7bfdf5073
4 changed files with 134 additions and 106 deletions

View File

@@ -357,7 +357,7 @@ pub fn JobConfigureImap(context: &Context) {
|| context.get_config_bool(Config::MvboxMove); || context.get_config_bool(Config::MvboxMove);
let imap = &context.inbox_thread.read().unwrap().imap; let imap = &context.inbox_thread.read().unwrap().imap;
if let Err(err) = imap.ensure_configured_folders(context, create_mvbox) { if let Err(err) = imap.ensure_configured_folders(context, create_mvbox) {
error!(context, "configuring folders failed: {:?}", err); warn!(context, "configuring folders failed: {:?}", err);
false false
} else { } else {
let res = imap.select_with_uidvalidity(context, "INBOX"); let res = imap.select_with_uidvalidity(context, "INBOX");

View File

@@ -36,24 +36,9 @@ pub enum Error {
InvalidMsgId, InvalidMsgId,
#[fail(display = "Watch folder not found {:?}", _0)] #[fail(display = "Watch folder not found {:?}", _0)]
WatchFolderNotFound(String), WatchFolderNotFound(String),
#[fail(display = "Connection Failed params: {}", _0)]
ImapConnectionFailed(String), #[fail(display = "error {:?}", _0)]
#[fail(display = "Could not get OAUTH token")] Other(String),
ImapOauthError,
#[fail(display = "Could not login as {}", _0)]
ImapLoginFailed(String),
#[fail(display = "Cannot idle")]
ImapMissesIdle,
#[fail(display = "Imap IDLE protocol failed to init/complete")]
ImapIdleProtocolFailed(String),
#[fail(display = "Imap IDLE failed to select folder {:?}", _0)]
ImapSelectFailed(String),
#[fail(display = "Connect without configured params")]
ConnectWithoutConfigure,
#[fail(display = "imap operation attempted while imap is torn down")]
ImapInTeardown,
#[fail(display = "No IMAP Connection established")]
ImapNoConnection,
} }
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;

View File

@@ -18,7 +18,6 @@ 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;
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};
@@ -31,6 +30,77 @@ use crate::wrapmime;
const DC_IMAP_SEEN: usize = 0x0001; const DC_IMAP_SEEN: usize = 0x0001;
type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Fail)]
pub enum Error {
#[fail(display = "IMAP Could not obtain imap-session object.")]
NoSession,
#[fail(display = "IMAP Connect without configured params")]
ConnectWithoutConfigure,
#[fail(display = "IMAP Connection Failed params: {}", _0)]
ConnectionFailed(String),
#[fail(display = "IMAP No Connection established")]
NoConnection,
#[fail(display = "IMAP Could not get OAUTH token")]
OauthError,
#[fail(display = "IMAP Could not login as {}", _0)]
LoginFailed(String),
#[fail(display = "IMAP Could not fetch {}", _0)]
FetchFailed(#[cause] async_imap::error::Error),
#[fail(display = "IMAP IDLE protocol failed to init/complete")]
IdleProtocolFailed(#[cause] async_imap::error::Error),
#[fail(display = "IMAP server does not have IDLE capability")]
IdleAbilityMissing,
#[fail(display = "IMAP Connection Lost or no connection established")]
ConnectionLost,
#[fail(display = "IMAP close/expunge failed: {}", _0)]
CloseExpungeFailed(#[cause] async_imap::error::Error),
#[fail(display = "IMAP Folder name invalid: {:?}", _0)]
BadFolderName(String),
#[fail(display = "IMAP operation attempted while it is torn down")]
InTeardown,
#[fail(display = "IMAP operation attempted while it is torn down")]
SqlError(#[cause] rusqlite::Error),
#[fail(display = "IMAP got error from elsewhere: {:?}", _0)]
WrappedError(#[cause] crate::error::Error),
#[fail(display = "IMAP other error: {:?}", _0)]
Other(String),
}
impl From<rusqlite::Error> for Error {
fn from(err: rusqlite::Error) -> Error {
Error::SqlError(err)
}
}
impl From<crate::error::Error> for Error {
fn from(err: crate::error::Error) -> Error {
Error::WrappedError(err)
}
}
impl From<Error> for crate::error::Error {
fn from(err: Error) -> crate::error::Error {
crate::error::Error::Other(err.to_string())
}
}
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Display, Clone, Copy, PartialEq, Eq)]
pub enum ImapActionResult { pub enum ImapActionResult {
Failed, Failed,
@@ -115,24 +185,6 @@ impl Default for ImapConfig {
} }
} }
#[derive(Debug, Fail)]
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(#[cause] async_imap::error::Error),
#[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 {
@@ -157,10 +209,10 @@ 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<(), Error> { fn setup_handle_if_needed(&self, context: &Context) -> Result<()> {
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::InTeardown);
} }
if self.should_reconnect() { if self.should_reconnect() {
@@ -222,7 +274,7 @@ impl Imap {
let res = client.authenticate("XOAUTH2", &auth).await; let res = client.authenticate("XOAUTH2", &auth).await;
res res
} else { } else {
return Err(Error::ImapOauthError); return Err(Error::OauthError);
} }
} else { } else {
let res = client.login(imap_user, imap_pw).await; let res = client.login(imap_user, imap_pw).await;
@@ -242,7 +294,7 @@ impl Imap {
}; };
// IMAP connection failures are reported to users // IMAP connection failures are reported to users
emit_event!(context, Event::ErrorNetwork(message)); emit_event!(context, Event::ErrorNetwork(message));
return Err(Error::ImapConnectionFailed(err.to_string())); return Err(Error::ConnectionFailed(err.to_string()));
} }
}; };
@@ -263,10 +315,7 @@ impl Imap {
Event::ErrorNetwork(format!("{} ({})", message, err)) Event::ErrorNetwork(format!("{} ({})", message, err))
); );
self.trigger_reconnect(); self.trigger_reconnect();
Err(Error::ImapLoginFailed(format!( Err(Error::LoginFailed(format!("cannot login as {}", imap_user)))
"cannot login as {}",
imap_user
)))
} }
} }
}) })
@@ -304,7 +353,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<(), Error> { pub fn connect_configured(&self, context: &Context) -> Result<()> {
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()
}) { }) {
@@ -320,9 +369,7 @@ impl Imap {
if self.connect(context, &param) { if self.connect(context, &param) {
self.ensure_configured_folders(context, true) self.ensure_configured_folders(context, true)
} else { } else {
Err(Error::ImapConnectionFailed( Err(Error::ConnectionFailed(format!("{}", param).to_string()))
format!("{}", param).to_string(),
))
} }
} }
@@ -408,11 +455,11 @@ impl Imap {
}); });
} }
pub fn fetch(&self, context: &Context, watch_folder: &str) -> Result<(), Error> { pub fn fetch(&self, context: &Context, watch_folder: &str) -> Result<()> {
task::block_on(async move { task::block_on(async move {
if !context.sql.is_open() { if !context.sql.is_open() {
// probably shutdown // probably shutdown
return Err(Error::ImapInTeardown); return Err(Error::InTeardown);
} }
while self while self
.fetch_from_single_folder(context, &watch_folder) .fetch_from_single_folder(context, &watch_folder)
@@ -430,12 +477,12 @@ impl Imap {
&self, &self,
context: &Context, context: &Context,
folder: Option<S>, folder: Option<S>,
) -> Result<(), SelectError> { ) -> Result<()> {
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 Err(SelectError::NoSession); 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 a new folder and the new folder is equal to the selected one, there's nothing to do.
@@ -462,11 +509,11 @@ impl Imap {
info!(context, "close/expunge succeeded"); info!(context, "close/expunge succeeded");
} }
Err(err) => { Err(err) => {
return Err(SelectError::CloseExpungeFailed(err)); return Err(Error::CloseExpungeFailed(err));
} }
} }
} else { } else {
return Err(SelectError::NoSession); return Err(Error::NoSession);
} }
} }
self.config.write().await.selected_folder_needs_expunge = false; self.config.write().await.selected_folder_needs_expunge = false;
@@ -491,19 +538,19 @@ impl Imap {
Err(async_imap::error::Error::ConnectionLost) => { Err(async_imap::error::Error::ConnectionLost) => {
self.trigger_reconnect(); self.trigger_reconnect();
self.config.write().await.selected_folder = None; self.config.write().await.selected_folder = None;
Err(SelectError::ConnectionLost) Err(Error::ConnectionLost)
} }
Err(async_imap::error::Error::Validate(_)) => { Err(async_imap::error::Error::Validate(_)) => {
Err(SelectError::BadFolderName(folder.as_ref().to_string())) Err(Error::BadFolderName(folder.as_ref().to_string()))
} }
Err(err) => { Err(err) => {
self.config.write().await.selected_folder = None; self.config.write().await.selected_folder = None;
self.trigger_reconnect(); self.trigger_reconnect();
Err(SelectError::Other(err.to_string())) Err(Error::Other(err.to_string()))
} }
} }
} else { } else {
Err(SelectError::NoSession) Err(Error::NoSession)
} }
} else { } else {
Ok(()) Ok(())
@@ -537,11 +584,9 @@ impl Imap {
&self, &self,
context: &Context, context: &Context,
folder: &str, folder: &str,
) -> Result<(u32, u32), Error> { ) -> Result<(u32, u32)> {
task::block_on(async move { task::block_on(async move {
if let Err(err) = self.select_folder(context, Some(folder)).await { self.select_folder(context, Some(folder)).await?;
bail!("could not select folder {:?}: {:?}", folder, err);
}
// compare last seen UIDVALIDITY against the current one // compare last seen UIDVALIDITY against the current one
let (uid_validity, last_seen_uid) = self.get_config_last_seen_uid(context, &folder); let (uid_validity, last_seen_uid) = self.get_config_last_seen_uid(context, &folder);
@@ -551,7 +596,10 @@ impl Imap {
let new_uid_validity = match mailbox.uid_validity { let new_uid_validity = match mailbox.uid_validity {
Some(v) => v, Some(v) => v,
None => bail!("Cannot get UIDVALIDITY for folder {:?}", folder), None => {
let s = format!("No UIDVALIDITY for folder {:?}", folder);
return Err(Error::Other(s));
}
}; };
if new_uid_validity == uid_validity { if new_uid_validity == uid_validity {
@@ -588,11 +636,11 @@ impl Imap {
match session.fetch(set, JUST_UID).await { match session.fetch(set, JUST_UID).await {
Ok(list) => list[0].uid.unwrap_or_default(), Ok(list) => list[0].uid.unwrap_or_default(),
Err(err) => { Err(err) => {
bail!("fetch failed: {:?}", err); return Err(Error::FetchFailed(err));
} }
} }
} else { } else {
return Err(Error::ImapNoConnection); return Err(Error::NoConnection);
} }
} }
}; };
@@ -614,7 +662,7 @@ impl Imap {
&self, &self,
context: &Context, context: &Context,
folder: S, folder: S,
) -> Result<bool, Error> { ) -> Result<bool> {
let (uid_validity, last_seen_uid) = let (uid_validity, last_seen_uid) =
self.select_with_uidvalidity(context, folder.as_ref())?; self.select_with_uidvalidity(context, folder.as_ref())?;
@@ -627,11 +675,11 @@ impl Imap {
match session.uid_fetch(set, PREFETCH_FLAGS).await { match session.uid_fetch(set, PREFETCH_FLAGS).await {
Ok(list) => list, Ok(list) => list,
Err(err) => { Err(err) => {
bail!("uid_fetch failed: {}", err); return Err(Error::FetchFailed(err));
} }
} }
} else { } else {
return Err(Error::ImapNoConnection); return Err(Error::NoConnection);
}; };
// prefetch info from all unfetched mails // prefetch info from all unfetched mails
@@ -645,7 +693,7 @@ impl Imap {
if cur_uid <= last_seen_uid { if cur_uid <= last_seen_uid {
warn!( warn!(
context, context,
"wrong uid {}, last seen was {}", cur_uid, last_seen_uid "unexpected uid {}, last seen was {}", cur_uid, last_seen_uid
); );
continue; continue;
} }
@@ -786,20 +834,15 @@ impl Imap {
1 1
} }
pub fn idle(&self, context: &Context, watch_folder: Option<String>) -> Result<(), Error> { pub fn idle(&self, context: &Context, watch_folder: Option<String>) -> Result<()> {
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::IdleAbilityMissing);
} }
self.setup_handle_if_needed(context)?; self.setup_handle_if_needed(context)?;
if let Err(err) = self.select_folder(context, watch_folder.clone()).await { self.select_folder(context, watch_folder.clone()).await?;
return Err(Error::ImapSelectFailed(format!(
"{:?}: {:?}",
watch_folder, err
)));
}
let session = self.session.lock().await.take(); let session = self.session.lock().await.take();
let timeout = Duration::from_secs(23 * 60); let timeout = Duration::from_secs(23 * 60);
@@ -809,7 +852,7 @@ impl Imap {
// typically also need to change the Insecure branch. // typically also need to change the Insecure branch.
IdleHandle::Secure(mut handle) => { IdleHandle::Secure(mut handle) => {
if let Err(err) = handle.init().await { if let Err(err) = handle.init().await {
return Err(Error::ImapIdleProtocolFailed(err.to_string())); return Err(Error::IdleProtocolFailed(err));
} }
let (idle_wait, interrupt) = handle.wait_with_timeout(timeout); let (idle_wait, interrupt) = handle.wait_with_timeout(timeout);
@@ -847,13 +890,13 @@ impl Imap {
// means that we waited long (with idle_wait) // means that we waited long (with idle_wait)
// but the network went away/changed // but the network went away/changed
self.trigger_reconnect(); self.trigger_reconnect();
return Err(Error::ImapIdleProtocolFailed(err.to_string())); return Err(Error::IdleProtocolFailed(err));
} }
} }
} }
IdleHandle::Insecure(mut handle) => { IdleHandle::Insecure(mut handle) => {
if let Err(err) = handle.init().await { if let Err(err) = handle.init().await {
return Err(Error::ImapIdleProtocolFailed(err.to_string())); return Err(Error::IdleProtocolFailed(err));
} }
let (idle_wait, interrupt) = handle.wait_with_timeout(timeout); let (idle_wait, interrupt) = handle.wait_with_timeout(timeout);
@@ -891,7 +934,7 @@ impl Imap {
// means that we waited long (with idle_wait) // means that we waited long (with idle_wait)
// but the network went away/changed // but the network went away/changed
self.trigger_reconnect(); self.trigger_reconnect();
return Err(Error::ImapIdleProtocolFailed(err.to_string())); return Err(Error::IdleProtocolFailed(err));
} }
} }
} }
@@ -1129,15 +1172,15 @@ impl Imap {
} }
match self.select_folder(context, Some(&folder)).await { match self.select_folder(context, Some(&folder)).await {
Ok(()) => None, Ok(()) => None,
Err(SelectError::ConnectionLost) => { Err(Error::ConnectionLost) => {
warn!(context, "Lost imap connection"); warn!(context, "Lost imap connection");
Some(ImapActionResult::RetryLater) Some(ImapActionResult::RetryLater)
} }
Err(SelectError::NoSession) => { Err(Error::NoSession) => {
warn!(context, "no imap session"); warn!(context, "no imap session");
Some(ImapActionResult::Failed) Some(ImapActionResult::Failed)
} }
Err(SelectError::BadFolderName(folder_name)) => { Err(Error::BadFolderName(folder_name)) => {
warn!(context, "invalid folder name: {:?}", folder_name); warn!(context, "invalid folder name: {:?}", folder_name);
Some(ImapActionResult::Failed) Some(ImapActionResult::Failed)
} }
@@ -1245,11 +1288,7 @@ impl Imap {
}) })
} }
pub fn ensure_configured_folders( pub fn ensure_configured_folders(&self, context: &Context, create_mvbox: bool) -> Result<()> {
&self,
context: &Context,
create_mvbox: bool,
) -> Result<(), Error> {
let folders_configured = context let folders_configured = context
.sql .sql
.get_raw_config_int(context, "folders_configured"); .get_raw_config_int(context, "folders_configured");
@@ -1260,10 +1299,9 @@ impl Imap {
} }
task::block_on(async move { task::block_on(async move {
ensure!( if !self.is_connected().await {
self.is_connected().await, return Err(Error::NoConnection);
"cannot configure folders: not connected" }
);
info!(context, "Configuring IMAP-folders."); info!(context, "Configuring IMAP-folders.");
@@ -1271,7 +1309,7 @@ impl Imap {
let folders = match self.list_folders(session, context).await { let folders = match self.list_folders(session, context).await {
Some(f) => f, Some(f) => f,
None => { None => {
bail!("could not obtain list of imap folders"); return Err(Error::Other("list_folders failed".to_string()));
} }
}; };
@@ -1490,12 +1528,17 @@ fn precheck_imf(context: &Context, rfc724_mid: &str, server_folder: &str, server
} }
} }
fn prefetch_get_message_id(prefetch_msg: &Fetch) -> Result<String, Error> { fn prefetch_get_message_id(prefetch_msg: &Fetch) -> Result<String> {
ensure!( if prefetch_msg.envelope().is_none() {
prefetch_msg.envelope().is_some(), return Err(Error::Other(
"Fetched message has no envelope" "prefectch: message has no envelope".to_string(),
); ));
}
let message_id = prefetch_msg.envelope().unwrap().message_id; let message_id = prefetch_msg.envelope().unwrap().message_id;
ensure!(message_id.is_some(), "No message ID found"); if message_id.is_none() {
wrapmime::parse_message_id(&message_id.unwrap()) return Err(Error::Other("prefetch: No message ID found".to_string()));
}
wrapmime::parse_message_id(&message_id.unwrap()).map_err(Into::into)
} }

View File

@@ -104,7 +104,7 @@ impl JobThread {
if let Some(watch_folder) = self.get_watch_folder(context) { if let Some(watch_folder) = self.get_watch_folder(context) {
let start = std::time::Instant::now(); let start = std::time::Instant::now();
info!(context, "{} started...", prefix); info!(context, "{} started...", prefix);
let res = self.imap.fetch(context, &watch_folder); let res = self.imap.fetch(context, &watch_folder).map_err(Into::into);
let elapsed = start.elapsed().as_millis(); let elapsed = start.elapsed().as_millis();
info!(context, "{} done in {:.3} ms.", prefix, elapsed); info!(context, "{} done in {:.3} ms.", prefix, elapsed);
@@ -113,7 +113,7 @@ impl JobThread {
Err(Error::WatchFolderNotFound("not-set".to_string())) Err(Error::WatchFolderNotFound("not-set".to_string()))
} }
} }
Err(err) => Err(err), Err(err) => Err(crate::error::Error::Other(err.to_string())),
} }
} }
@@ -176,7 +176,7 @@ impl JobThread {
info!(context, "{} ended...", prefix); info!(context, "{} ended...", prefix);
match res { match res {
Ok(()) => false, Ok(()) => false,
Err(Error::ImapMissesIdle) => true, // we have to do fake_idle Err(crate::imap::Error::IdleAbilityMissing) => true, // we have to do fake_idle
Err(err) => { Err(err) => {
warn!(context, "{} failed: {} -> reconnecting", prefix, err); warn!(context, "{} failed: {} -> reconnecting", prefix, err);
// something is borked, let's start afresh on the next occassion // something is borked, let's start afresh on the next occassion