mirror of
https://github.com/chatmail/core.git
synced 2026-07-03 04:55:14 +03:00
Compare commits
10 Commits
hpk_imap_e
...
imap_error
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a770a42bc0 | ||
|
|
c7bfdf5073 | ||
|
|
603d55114b | ||
|
|
9d18db9cae | ||
|
|
d14c6ea202 | ||
|
|
7be5fe925a | ||
|
|
8f43d7fa34 | ||
|
|
b6e9bcee3c | ||
|
|
2dbef704e9 | ||
|
|
74a4691f29 |
260
src/blob.rs
260
src/blob.rs
@@ -32,11 +32,11 @@ impl<'a> BlobObject<'a> {
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// [BlobErrorKind::CreateFailure] is used when the file could not
|
||||
/// [BlobError::CreateFailure] is used when the file could not
|
||||
/// be created. You can expect [BlobError.cause] to contain an
|
||||
/// underlying error.
|
||||
///
|
||||
/// [BlobErrorKind::WriteFailure] is used when the file could not
|
||||
/// [BlobError::WriteFailure] is used when the file could not
|
||||
/// be written to. You can expect [BlobError.cause] to contain an
|
||||
/// underlying error.
|
||||
pub fn create(
|
||||
@@ -48,7 +48,12 @@ impl<'a> BlobObject<'a> {
|
||||
let (stem, ext) = BlobObject::sanitise_name(suggested_name.as_ref());
|
||||
let (name, mut file) = BlobObject::create_new_file(&blobdir, &stem, &ext)?;
|
||||
file.write_all(data)
|
||||
.map_err(|err| BlobError::new_write_failure(blobdir, &name, err))?;
|
||||
.map_err(|err| BlobError::WriteFailure {
|
||||
blobdir: blobdir.to_path_buf(),
|
||||
blobname: name.clone(),
|
||||
cause: err,
|
||||
backtrace: failure::Backtrace::new(),
|
||||
})?;
|
||||
let blob = BlobObject {
|
||||
blobdir,
|
||||
name: format!("$BLOBDIR/{}", name),
|
||||
@@ -71,18 +76,25 @@ impl<'a> BlobObject<'a> {
|
||||
Ok(file) => return Ok((name, file)),
|
||||
Err(err) => {
|
||||
if attempt == max_attempt {
|
||||
return Err(BlobError::new_create_failure(dir, &name, err));
|
||||
return Err(BlobError::CreateFailure {
|
||||
blobdir: dir.to_path_buf(),
|
||||
blobname: name,
|
||||
cause: err,
|
||||
backtrace: failure::Backtrace::new(),
|
||||
});
|
||||
} else {
|
||||
name = format!("{}-{}{}", stem, rand::random::<u32>(), ext);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(BlobError::new_create_failure(
|
||||
dir,
|
||||
&name,
|
||||
format_err!("Unreachable code - supposedly"),
|
||||
))
|
||||
// This is supposed to be unreachable, but the compiler doesn't know.
|
||||
Err(BlobError::CreateFailure {
|
||||
blobdir: dir.to_path_buf(),
|
||||
blobname: name,
|
||||
cause: std::io::Error::new(std::io::ErrorKind::Other, "supposedly unreachable"),
|
||||
backtrace: failure::Backtrace::new(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a new blob object with unique name by copying an existing file.
|
||||
@@ -95,24 +107,35 @@ impl<'a> BlobObject<'a> {
|
||||
/// # Errors
|
||||
///
|
||||
/// In addition to the errors in [BlobObject::create] the
|
||||
/// [BlobErrorKind::CopyFailure] is used when the data can not be
|
||||
/// [BlobError::CopyFailure] is used when the data can not be
|
||||
/// copied.
|
||||
pub fn create_and_copy(
|
||||
context: &'a Context,
|
||||
src: impl AsRef<Path>,
|
||||
) -> std::result::Result<BlobObject<'a>, BlobError> {
|
||||
let mut src_file = fs::File::open(src.as_ref()).map_err(|err| {
|
||||
BlobError::new_copy_failure(context.get_blobdir(), "", src.as_ref(), err)
|
||||
let mut src_file = fs::File::open(src.as_ref()).map_err(|err| BlobError::CopyFailure {
|
||||
blobdir: context.get_blobdir().to_path_buf(),
|
||||
blobname: String::from(""),
|
||||
src: src.as_ref().to_path_buf(),
|
||||
cause: err,
|
||||
backtrace: failure::Backtrace::new(),
|
||||
})?;
|
||||
let (stem, ext) = BlobObject::sanitise_name(&src.as_ref().to_string_lossy());
|
||||
let (name, mut dst_file) = BlobObject::create_new_file(context.get_blobdir(), &stem, &ext)?;
|
||||
let name_for_err = name.clone();
|
||||
std::io::copy(&mut src_file, &mut dst_file).map_err(|err| {
|
||||
{
|
||||
// Attempt to remove the failed file, swallow errors resulting from that.
|
||||
let path = context.get_blobdir().join(&name);
|
||||
let path = context.get_blobdir().join(&name_for_err);
|
||||
fs::remove_file(path).ok();
|
||||
}
|
||||
BlobError::new_copy_failure(context.get_blobdir(), &name, src.as_ref(), err)
|
||||
BlobError::CopyFailure {
|
||||
blobdir: context.get_blobdir().to_path_buf(),
|
||||
blobname: name_for_err,
|
||||
src: src.as_ref().to_path_buf(),
|
||||
cause: err,
|
||||
backtrace: failure::Backtrace::new(),
|
||||
}
|
||||
})?;
|
||||
let blob = BlobObject {
|
||||
blobdir: context.get_blobdir(),
|
||||
@@ -156,10 +179,10 @@ impl<'a> BlobObject<'a> {
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// [BlobErrorKind::WrongBlobdir] is used if the path is not in
|
||||
/// [BlobError::WrongBlobdir] is used if the path is not in
|
||||
/// the blob directory.
|
||||
///
|
||||
/// [BlobErrorKind::WrongName] is used if the file name does not
|
||||
/// [BlobError::WrongName] is used if the file name does not
|
||||
/// remain identical after sanitisation.
|
||||
pub fn from_path(
|
||||
context: &Context,
|
||||
@@ -168,13 +191,21 @@ impl<'a> BlobObject<'a> {
|
||||
let rel_path = path
|
||||
.as_ref()
|
||||
.strip_prefix(context.get_blobdir())
|
||||
.map_err(|_| BlobError::new_wrong_blobdir(context.get_blobdir(), path.as_ref()))?;
|
||||
.map_err(|_| BlobError::WrongBlobdir {
|
||||
blobdir: context.get_blobdir().to_path_buf(),
|
||||
src: path.as_ref().to_path_buf(),
|
||||
backtrace: failure::Backtrace::new(),
|
||||
})?;
|
||||
if !BlobObject::is_acceptible_blob_name(&rel_path) {
|
||||
return Err(BlobError::new_wrong_name(path.as_ref()));
|
||||
return Err(BlobError::WrongName {
|
||||
blobname: path.as_ref().to_path_buf(),
|
||||
backtrace: failure::Backtrace::new(),
|
||||
});
|
||||
}
|
||||
let name = rel_path
|
||||
.to_str()
|
||||
.ok_or_else(|| BlobError::new_wrong_name(path.as_ref()))?;
|
||||
let name = rel_path.to_str().ok_or_else(|| BlobError::WrongName {
|
||||
blobname: path.as_ref().to_path_buf(),
|
||||
backtrace: failure::Backtrace::new(),
|
||||
})?;
|
||||
BlobObject::from_name(context, name.to_string())
|
||||
}
|
||||
|
||||
@@ -187,7 +218,7 @@ impl<'a> BlobObject<'a> {
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// [BlobErrorKind::WrongName] is used if the name is not a valid
|
||||
/// [BlobError::WrongName] is used if the name is not a valid
|
||||
/// blobname, i.e. if [BlobObject::sanitise_name] does modify the
|
||||
/// provided name.
|
||||
pub fn from_name(
|
||||
@@ -199,7 +230,10 @@ impl<'a> BlobObject<'a> {
|
||||
false => name,
|
||||
};
|
||||
if !BlobObject::is_acceptible_blob_name(&name) {
|
||||
return Err(BlobError::new_wrong_name(name));
|
||||
return Err(BlobError::WrongName {
|
||||
blobname: PathBuf::from(name),
|
||||
backtrace: failure::Backtrace::new(),
|
||||
});
|
||||
}
|
||||
Ok(BlobObject {
|
||||
blobdir: context.get_blobdir(),
|
||||
@@ -324,171 +358,50 @@ impl<'a> fmt::Display for BlobObject<'a> {
|
||||
}
|
||||
|
||||
/// Errors for the [BlobObject].
|
||||
///
|
||||
/// To keep the return type small and thus the happy path fast this
|
||||
/// stores everything on the heap.
|
||||
#[derive(Debug)]
|
||||
pub struct BlobError {
|
||||
inner: Box<BlobErrorInner>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct BlobErrorInner {
|
||||
kind: BlobErrorKind,
|
||||
data: BlobErrorData,
|
||||
backtrace: failure::Backtrace,
|
||||
}
|
||||
|
||||
/// Error kind for [BlobError].
|
||||
///
|
||||
/// Each error kind has associated data in the [BlobErrorData].
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum BlobErrorKind {
|
||||
/// Failed to create the blob.
|
||||
CreateFailure,
|
||||
/// Failed to write data to blob.
|
||||
WriteFailure,
|
||||
/// Failed to copy data to blob.
|
||||
CopyFailure,
|
||||
/// Blob is not in the blobdir.
|
||||
WrongBlobdir,
|
||||
/// Blob has a bad name.
|
||||
///
|
||||
/// E.g. the name is not sanitised correctly or contains a
|
||||
/// sub-directory.
|
||||
WrongName,
|
||||
}
|
||||
|
||||
/// Associated data for each [BlobError] error kind.
|
||||
///
|
||||
/// This is not stored directly on the [BlobErrorKind] so that the
|
||||
/// kind can stay trivially Copy and Eq. It is however possible to
|
||||
/// create a [BlobError] with mismatching [BlobErrorKind] and
|
||||
/// [BlobErrorData], don't do that.
|
||||
///
|
||||
/// Any blobname stored here is the bare name, without the `$BLOBDIR`
|
||||
/// prefix. All data is owned so that errors do not need to be tied
|
||||
/// to any lifetimes.
|
||||
#[derive(Debug)]
|
||||
enum BlobErrorData {
|
||||
#[derive(Fail, Debug)]
|
||||
pub enum BlobError {
|
||||
CreateFailure {
|
||||
blobdir: PathBuf,
|
||||
blobname: String,
|
||||
cause: failure::Error,
|
||||
#[cause]
|
||||
cause: std::io::Error,
|
||||
backtrace: failure::Backtrace,
|
||||
},
|
||||
WriteFailure {
|
||||
blobdir: PathBuf,
|
||||
blobname: String,
|
||||
cause: failure::Error,
|
||||
#[cause]
|
||||
cause: std::io::Error,
|
||||
backtrace: failure::Backtrace,
|
||||
},
|
||||
CopyFailure {
|
||||
blobdir: PathBuf,
|
||||
blobname: String,
|
||||
src: PathBuf,
|
||||
cause: failure::Error,
|
||||
#[cause]
|
||||
cause: std::io::Error,
|
||||
backtrace: failure::Backtrace,
|
||||
},
|
||||
WrongBlobdir {
|
||||
blobdir: PathBuf,
|
||||
src: PathBuf,
|
||||
backtrace: failure::Backtrace,
|
||||
},
|
||||
WrongName {
|
||||
blobname: PathBuf,
|
||||
backtrace: failure::Backtrace,
|
||||
},
|
||||
}
|
||||
|
||||
impl BlobError {
|
||||
pub fn kind(&self) -> BlobErrorKind {
|
||||
self.inner.kind
|
||||
}
|
||||
|
||||
fn new_create_failure(
|
||||
blobdir: impl Into<PathBuf>,
|
||||
blobname: impl Into<String>,
|
||||
cause: impl Into<failure::Error>,
|
||||
) -> BlobError {
|
||||
BlobError {
|
||||
inner: Box::new(BlobErrorInner {
|
||||
kind: BlobErrorKind::CreateFailure,
|
||||
data: BlobErrorData::CreateFailure {
|
||||
blobdir: blobdir.into(),
|
||||
blobname: blobname.into(),
|
||||
cause: cause.into(),
|
||||
},
|
||||
backtrace: failure::Backtrace::new(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn new_write_failure(
|
||||
blobdir: impl Into<PathBuf>,
|
||||
blobname: impl Into<String>,
|
||||
cause: impl Into<failure::Error>,
|
||||
) -> BlobError {
|
||||
BlobError {
|
||||
inner: Box::new(BlobErrorInner {
|
||||
kind: BlobErrorKind::WriteFailure,
|
||||
data: BlobErrorData::WriteFailure {
|
||||
blobdir: blobdir.into(),
|
||||
blobname: blobname.into(),
|
||||
cause: cause.into(),
|
||||
},
|
||||
backtrace: failure::Backtrace::new(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn new_copy_failure(
|
||||
blobdir: impl Into<PathBuf>,
|
||||
blobname: impl Into<String>,
|
||||
src: impl Into<PathBuf>,
|
||||
cause: impl Into<failure::Error>,
|
||||
) -> BlobError {
|
||||
BlobError {
|
||||
inner: Box::new(BlobErrorInner {
|
||||
kind: BlobErrorKind::CopyFailure,
|
||||
data: BlobErrorData::CopyFailure {
|
||||
blobdir: blobdir.into(),
|
||||
blobname: blobname.into(),
|
||||
src: src.into(),
|
||||
cause: cause.into(),
|
||||
},
|
||||
backtrace: failure::Backtrace::new(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn new_wrong_blobdir(blobdir: impl Into<PathBuf>, src: impl Into<PathBuf>) -> BlobError {
|
||||
BlobError {
|
||||
inner: Box::new(BlobErrorInner {
|
||||
kind: BlobErrorKind::WrongBlobdir,
|
||||
data: BlobErrorData::WrongBlobdir {
|
||||
blobdir: blobdir.into(),
|
||||
src: src.into(),
|
||||
},
|
||||
backtrace: failure::Backtrace::new(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn new_wrong_name(blobname: impl Into<PathBuf>) -> BlobError {
|
||||
BlobError {
|
||||
inner: Box::new(BlobErrorInner {
|
||||
kind: BlobErrorKind::WrongName,
|
||||
data: BlobErrorData::WrongName {
|
||||
blobname: blobname.into(),
|
||||
},
|
||||
backtrace: failure::Backtrace::new(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Implementing Display is done by hand because the failure
|
||||
// #[fail(display = "...")] syntax does not allow using
|
||||
// `blobdir.display()`.
|
||||
impl fmt::Display for BlobError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
// Match on the data rather than kind, they are equivalent for
|
||||
// identifying purposes but contain the actual data we need.
|
||||
match &self.inner.data {
|
||||
BlobErrorData::CreateFailure {
|
||||
match &self {
|
||||
BlobError::CreateFailure {
|
||||
blobdir, blobname, ..
|
||||
} => write!(
|
||||
f,
|
||||
@@ -496,7 +409,7 @@ impl fmt::Display for BlobError {
|
||||
blobname,
|
||||
blobdir.display()
|
||||
),
|
||||
BlobErrorData::WriteFailure {
|
||||
BlobError::WriteFailure {
|
||||
blobdir, blobname, ..
|
||||
} => write!(
|
||||
f,
|
||||
@@ -504,7 +417,7 @@ impl fmt::Display for BlobError {
|
||||
blobname,
|
||||
blobdir.display()
|
||||
),
|
||||
BlobErrorData::CopyFailure {
|
||||
BlobError::CopyFailure {
|
||||
blobdir,
|
||||
blobname,
|
||||
src,
|
||||
@@ -516,34 +429,19 @@ impl fmt::Display for BlobError {
|
||||
blobname,
|
||||
blobdir.display(),
|
||||
),
|
||||
BlobErrorData::WrongBlobdir { blobdir, src } => write!(
|
||||
BlobError::WrongBlobdir { blobdir, src, .. } => write!(
|
||||
f,
|
||||
"File path {} is not in blobdir {}",
|
||||
src.display(),
|
||||
blobdir.display(),
|
||||
),
|
||||
BlobErrorData::WrongName { blobname } => {
|
||||
BlobError::WrongName { blobname, .. } => {
|
||||
write!(f, "Blob has a bad name: {}", blobname.display(),)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl failure::Fail for BlobError {
|
||||
fn cause(&self) -> Option<&dyn failure::Fail> {
|
||||
match &self.inner.data {
|
||||
BlobErrorData::CreateFailure { cause, .. }
|
||||
| BlobErrorData::WriteFailure { cause, .. }
|
||||
| BlobErrorData::CopyFailure { cause, .. } => Some(cause.as_fail()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn backtrace(&self) -> Option<&failure::Backtrace> {
|
||||
Some(&self.inner.backtrace)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::path::{Path, PathBuf};
|
||||
use itertools::Itertools;
|
||||
use num_traits::FromPrimitive;
|
||||
|
||||
use crate::blob::{BlobErrorKind, BlobObject};
|
||||
use crate::blob::{BlobError, BlobObject};
|
||||
use crate::chatlist::*;
|
||||
use crate::config::*;
|
||||
use crate::constants::*;
|
||||
@@ -1797,8 +1797,8 @@ pub fn set_chat_profile_image(
|
||||
));
|
||||
} else {
|
||||
let image_blob = BlobObject::from_path(context, Path::new(new_image.as_ref())).or_else(
|
||||
|err| match err.kind() {
|
||||
BlobErrorKind::WrongBlobdir => {
|
||||
|err| match err {
|
||||
BlobError::WrongBlobdir { .. } => {
|
||||
BlobObject::create_and_copy(context, Path::new(new_image.as_ref()))
|
||||
}
|
||||
_ => Err(err),
|
||||
|
||||
@@ -357,7 +357,7 @@ pub fn JobConfigureImap(context: &Context) {
|
||||
|| context.get_config_bool(Config::MvboxMove);
|
||||
let imap = &context.inbox_thread.read().unwrap().imap;
|
||||
if let Err(err) = imap.ensure_configured_folders(context, create_mvbox) {
|
||||
error!(context, "configuring folders failed: {:?}", err);
|
||||
warn!(context, "configuring folders failed: {:?}", err);
|
||||
false
|
||||
} else {
|
||||
let res = imap.select_with_uidvalidity(context, "INBOX");
|
||||
|
||||
18
src/error.rs
18
src/error.rs
@@ -36,24 +36,6 @@ pub enum Error {
|
||||
InvalidMsgId,
|
||||
#[fail(display = "Watch folder not found {:?}", _0)]
|
||||
WatchFolderNotFound(String),
|
||||
#[fail(display = "Connection Failed params: {}", _0)]
|
||||
ImapConnectionFailed(String),
|
||||
#[fail(display = "Could not get OAUTH token")]
|
||||
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>;
|
||||
|
||||
211
src/imap.rs
211
src/imap.rs
@@ -18,7 +18,6 @@ use async_std::task;
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_receive_imf::dc_receive_imf;
|
||||
use crate::error::Error;
|
||||
use crate::events::Event;
|
||||
use crate::imap_client::*;
|
||||
use crate::job::{job_add, Action};
|
||||
@@ -31,6 +30,77 @@ use crate::wrapmime;
|
||||
|
||||
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::Message(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ImapActionResult {
|
||||
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 {
|
||||
pub fn new() -> Self {
|
||||
Imap {
|
||||
@@ -157,10 +209,10 @@ impl Imap {
|
||||
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 {
|
||||
if self.config.read().await.imap_server.is_empty() {
|
||||
return Err(Error::ImapInTeardown);
|
||||
return Err(Error::InTeardown);
|
||||
}
|
||||
|
||||
if self.should_reconnect() {
|
||||
@@ -222,7 +274,7 @@ impl Imap {
|
||||
let res = client.authenticate("XOAUTH2", &auth).await;
|
||||
res
|
||||
} else {
|
||||
return Err(Error::ImapOauthError);
|
||||
return Err(Error::OauthError);
|
||||
}
|
||||
} else {
|
||||
let res = client.login(imap_user, imap_pw).await;
|
||||
@@ -242,7 +294,7 @@ impl Imap {
|
||||
};
|
||||
// IMAP connection failures are reported to users
|
||||
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))
|
||||
);
|
||||
self.trigger_reconnect();
|
||||
Err(Error::ImapLoginFailed(format!(
|
||||
"cannot login as {}",
|
||||
imap_user
|
||||
)))
|
||||
Err(Error::LoginFailed(format!("cannot login as {}", imap_user)))
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -304,7 +353,7 @@ impl Imap {
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
self.is_connected().await && !self.should_reconnect()
|
||||
}) {
|
||||
@@ -320,9 +369,7 @@ impl Imap {
|
||||
if self.connect(context, ¶m) {
|
||||
self.ensure_configured_folders(context, true)
|
||||
} else {
|
||||
Err(Error::ImapConnectionFailed(
|
||||
format!("{}", param).to_string(),
|
||||
))
|
||||
Err(Error::ConnectionFailed(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 {
|
||||
if !context.sql.is_open() {
|
||||
// probably shutdown
|
||||
return Err(Error::ImapInTeardown);
|
||||
return Err(Error::InTeardown);
|
||||
}
|
||||
while self
|
||||
.fetch_from_single_folder(context, &watch_folder)
|
||||
@@ -430,12 +477,12 @@ impl Imap {
|
||||
&self,
|
||||
context: &Context,
|
||||
folder: Option<S>,
|
||||
) -> Result<(), SelectError> {
|
||||
) -> Result<()> {
|
||||
if self.session.lock().await.is_none() {
|
||||
let mut cfg = self.config.write().await;
|
||||
cfg.selected_folder = None;
|
||||
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.
|
||||
@@ -462,11 +509,11 @@ impl Imap {
|
||||
info!(context, "close/expunge succeeded");
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(SelectError::CloseExpungeFailed(err));
|
||||
return Err(Error::CloseExpungeFailed(err));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(SelectError::NoSession);
|
||||
return Err(Error::NoSession);
|
||||
}
|
||||
}
|
||||
self.config.write().await.selected_folder_needs_expunge = false;
|
||||
@@ -491,19 +538,19 @@ impl Imap {
|
||||
Err(async_imap::error::Error::ConnectionLost) => {
|
||||
self.trigger_reconnect();
|
||||
self.config.write().await.selected_folder = None;
|
||||
Err(SelectError::ConnectionLost)
|
||||
Err(Error::ConnectionLost)
|
||||
}
|
||||
Err(async_imap::error::Error::Validate(_)) => {
|
||||
Err(SelectError::BadFolderName(folder.as_ref().to_string()))
|
||||
Err(Error::BadFolderName(folder.as_ref().to_string()))
|
||||
}
|
||||
Err(err) => {
|
||||
self.config.write().await.selected_folder = None;
|
||||
self.trigger_reconnect();
|
||||
Err(SelectError::Other(err.to_string()))
|
||||
Err(Error::Other(err.to_string()))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(SelectError::NoSession)
|
||||
Err(Error::NoSession)
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
@@ -537,11 +584,9 @@ impl Imap {
|
||||
&self,
|
||||
context: &Context,
|
||||
folder: &str,
|
||||
) -> Result<(u32, u32), Error> {
|
||||
) -> Result<(u32, u32)> {
|
||||
task::block_on(async move {
|
||||
if let Err(err) = self.select_folder(context, Some(folder)).await {
|
||||
bail!("could not select folder {:?}: {:?}", folder, err);
|
||||
}
|
||||
self.select_folder(context, Some(folder)).await?;
|
||||
|
||||
// compare last seen UIDVALIDITY against the current one
|
||||
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 {
|
||||
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 {
|
||||
@@ -588,11 +636,11 @@ impl Imap {
|
||||
match session.fetch(set, JUST_UID).await {
|
||||
Ok(list) => list[0].uid.unwrap_or_default(),
|
||||
Err(err) => {
|
||||
bail!("fetch failed: {:?}", err);
|
||||
return Err(Error::FetchFailed(err));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(Error::ImapNoConnection);
|
||||
return Err(Error::NoConnection);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -614,7 +662,7 @@ impl Imap {
|
||||
&self,
|
||||
context: &Context,
|
||||
folder: S,
|
||||
) -> Result<bool, Error> {
|
||||
) -> Result<bool> {
|
||||
let (uid_validity, last_seen_uid) =
|
||||
self.select_with_uidvalidity(context, folder.as_ref())?;
|
||||
|
||||
@@ -627,11 +675,11 @@ impl Imap {
|
||||
match session.uid_fetch(set, PREFETCH_FLAGS).await {
|
||||
Ok(list) => list,
|
||||
Err(err) => {
|
||||
bail!("uid_fetch failed: {}", err);
|
||||
return Err(Error::FetchFailed(err));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(Error::ImapNoConnection);
|
||||
return Err(Error::NoConnection);
|
||||
};
|
||||
|
||||
// prefetch info from all unfetched mails
|
||||
@@ -645,7 +693,7 @@ impl Imap {
|
||||
if cur_uid <= last_seen_uid {
|
||||
warn!(
|
||||
context,
|
||||
"wrong uid {}, last seen was {}", cur_uid, last_seen_uid
|
||||
"unexpected uid {}, last seen was {}", cur_uid, last_seen_uid
|
||||
);
|
||||
continue;
|
||||
}
|
||||
@@ -786,20 +834,15 @@ impl Imap {
|
||||
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 {
|
||||
if !self.config.read().await.can_idle {
|
||||
return Err(Error::ImapMissesIdle);
|
||||
return Err(Error::IdleAbilityMissing);
|
||||
}
|
||||
|
||||
self.setup_handle_if_needed(context)?;
|
||||
|
||||
if let Err(err) = self.select_folder(context, watch_folder.clone()).await {
|
||||
return Err(Error::ImapSelectFailed(format!(
|
||||
"{:?}: {:?}",
|
||||
watch_folder, err
|
||||
)));
|
||||
}
|
||||
self.select_folder(context, watch_folder.clone()).await?;
|
||||
|
||||
let session = self.session.lock().await.take();
|
||||
let timeout = Duration::from_secs(23 * 60);
|
||||
@@ -809,7 +852,7 @@ impl Imap {
|
||||
// typically also need to change the Insecure branch.
|
||||
IdleHandle::Secure(mut handle) => {
|
||||
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);
|
||||
@@ -847,13 +890,13 @@ impl Imap {
|
||||
// means that we waited long (with idle_wait)
|
||||
// but the network went away/changed
|
||||
self.trigger_reconnect();
|
||||
return Err(Error::ImapIdleProtocolFailed(err.to_string()));
|
||||
return Err(Error::IdleProtocolFailed(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
IdleHandle::Insecure(mut handle) => {
|
||||
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);
|
||||
@@ -891,7 +934,7 @@ impl Imap {
|
||||
// means that we waited long (with idle_wait)
|
||||
// but the network went away/changed
|
||||
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 {
|
||||
Ok(()) => None,
|
||||
Err(SelectError::ConnectionLost) => {
|
||||
Err(Error::ConnectionLost) => {
|
||||
warn!(context, "Lost imap connection");
|
||||
Some(ImapActionResult::RetryLater)
|
||||
}
|
||||
Err(SelectError::NoSession) => {
|
||||
Err(Error::NoSession) => {
|
||||
warn!(context, "no imap session");
|
||||
Some(ImapActionResult::Failed)
|
||||
}
|
||||
Err(SelectError::BadFolderName(folder_name)) => {
|
||||
Err(Error::BadFolderName(folder_name)) => {
|
||||
warn!(context, "invalid folder name: {:?}", folder_name);
|
||||
Some(ImapActionResult::Failed)
|
||||
}
|
||||
@@ -1245,11 +1288,7 @@ impl Imap {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn ensure_configured_folders(
|
||||
&self,
|
||||
context: &Context,
|
||||
create_mvbox: bool,
|
||||
) -> Result<(), Error> {
|
||||
pub fn ensure_configured_folders(&self, context: &Context, create_mvbox: bool) -> Result<()> {
|
||||
let folders_configured = context
|
||||
.sql
|
||||
.get_raw_config_int(context, "folders_configured");
|
||||
@@ -1260,10 +1299,9 @@ impl Imap {
|
||||
}
|
||||
|
||||
task::block_on(async move {
|
||||
ensure!(
|
||||
self.is_connected().await,
|
||||
"cannot configure folders: not connected"
|
||||
);
|
||||
if !self.is_connected().await {
|
||||
return Err(Error::NoConnection);
|
||||
}
|
||||
|
||||
info!(context, "Configuring IMAP-folders.");
|
||||
|
||||
@@ -1271,7 +1309,7 @@ impl Imap {
|
||||
let folders = match self.list_folders(session, context).await {
|
||||
Some(f) => f,
|
||||
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> {
|
||||
ensure!(
|
||||
prefetch_msg.envelope().is_some(),
|
||||
"Fetched message has no envelope"
|
||||
);
|
||||
fn prefetch_get_message_id(prefetch_msg: &Fetch) -> Result<String> {
|
||||
if prefetch_msg.envelope().is_none() {
|
||||
return Err(Error::Other(
|
||||
"prefectch: message has no envelope".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let message_id = prefetch_msg.envelope().unwrap().message_id;
|
||||
ensure!(message_id.is_some(), "No message ID found");
|
||||
wrapmime::parse_message_id(&message_id.unwrap())
|
||||
if message_id.is_none() {
|
||||
return Err(Error::Other("prefetch: No message ID found".to_string()));
|
||||
}
|
||||
|
||||
wrapmime::parse_message_id(&message_id.unwrap()).map_err(Into::into)
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ impl JobThread {
|
||||
if let Some(watch_folder) = self.get_watch_folder(context) {
|
||||
let start = std::time::Instant::now();
|
||||
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();
|
||||
info!(context, "{} done in {:.3} ms.", prefix, elapsed);
|
||||
|
||||
@@ -113,7 +113,7 @@ impl JobThread {
|
||||
Err(Error::WatchFolderNotFound("not-set".to_string()))
|
||||
}
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
Err(err) => Err(crate::error::Error::Message(err.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ impl JobThread {
|
||||
info!(context, "{} ended...", prefix);
|
||||
match res {
|
||||
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) => {
|
||||
warn!(context, "{} failed: {} -> reconnecting", prefix, err);
|
||||
// something is borked, let's start afresh on the next occassion
|
||||
|
||||
@@ -321,7 +321,6 @@ mod tests {
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::blob::BlobErrorKind;
|
||||
use crate::test_utils::*;
|
||||
|
||||
#[test]
|
||||
@@ -404,7 +403,10 @@ mod tests {
|
||||
|
||||
// Blob does not exist yet, expect BlobError.
|
||||
let err = p.get_blob(Param::File, &t.ctx, false).unwrap_err();
|
||||
assert_eq!(err.kind(), BlobErrorKind::WrongBlobdir);
|
||||
match err {
|
||||
BlobError::WrongBlobdir { .. } => (),
|
||||
_ => panic!("wrong error type/variant: {:?}", err),
|
||||
}
|
||||
|
||||
fs::write(fname, b"boo").unwrap();
|
||||
let blob = p.get_blob(Param::File, &t.ctx, true).unwrap().unwrap();
|
||||
|
||||
Reference in New Issue
Block a user