Error handling refactoring

- Replace .ok_or_else() and .map_err() with anyhow::Context where possible.
- Use .context() to check Option for None when it's an error
- Resultify Chatlist.get_chat_id()
- Add useful .context() to some errors
- IMAP error handling cleanup
This commit is contained in:
link2xt
2022-01-05 01:51:11 +00:00
parent 29c58efeb3
commit bfa641cea8
24 changed files with 252 additions and 283 deletions

View File

@@ -2443,7 +2443,14 @@ pub unsafe extern "C" fn dc_chatlist_get_chat_id(
return 0; return 0;
} }
let ffi_list = &*chatlist; let ffi_list = &*chatlist;
ffi_list.list.get_chat_id(index as usize).to_u32() let ctx = &*ffi_list.context;
match ffi_list.list.get_chat_id(index as usize) {
Ok(chat_id) => chat_id.to_u32(),
Err(err) => {
warn!(ctx, "get_chat_id failed: {}", err);
0
}
}
} }
#[no_mangle] #[no_mangle]

View File

@@ -563,7 +563,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
); );
for i in (0..cnt).rev() { for i in (0..cnt).rev() {
let chat = Chat::load_from_db(&context, chatlist.get_chat_id(i)).await?; let chat = Chat::load_from_db(&context, chatlist.get_chat_id(i)?).await?;
println!( println!(
"{}#{}: {} [{} fresh] {}{}{}{}", "{}#{}: {} [{} fresh] {}{}{}{}",
chat_prefix(&chat), chat_prefix(&chat),
@@ -1142,7 +1142,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
if 0 != i { if 0 != i {
res += ", "; res += ", ";
} }
let chat = Chat::load_from_db(&context, chatlist.get_chat_id(i)).await?; let chat = Chat::load_from_db(&context, chatlist.get_chat_id(i)?).await?;
res += &format!("{}#{}", chat_prefix(&chat), chat.get_id()); res += &format!("{}#{}", chat_prefix(&chat), chat.get_id());
} }
} }

View File

@@ -56,7 +56,9 @@ impl Accounts {
let config_file = dir.join(CONFIG_NAME); let config_file = dir.join(CONFIG_NAME);
ensure!(config_file.exists().await, "accounts.toml does not exist"); ensure!(config_file.exists().await, "accounts.toml does not exist");
let config = Config::from_file(config_file).await?; let config = Config::from_file(config_file)
.await
.context("failed to load accounts config")?;
let accounts = config.load_accounts().await?; let accounts = config.load_accounts().await?;
let emitter = EventEmitter::new(); let emitter = EventEmitter::new();

View File

@@ -2,7 +2,7 @@
//! //!
//! Parse and create [Autocrypt-headers](https://autocrypt.org/en/latest/level1.html#the-autocrypt-header). //! Parse and create [Autocrypt-headers](https://autocrypt.org/en/latest/level1.html#the-autocrypt-header).
use anyhow::{bail, format_err, Error, Result}; use anyhow::{bail, Context as _, Error, Result};
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::str::FromStr; use std::str::FromStr;
use std::{fmt, str}; use std::{fmt, str};
@@ -139,15 +139,14 @@ impl str::FromStr for Aheader {
}; };
let public_key: SignedPublicKey = attributes let public_key: SignedPublicKey = attributes
.remove("keydata") .remove("keydata")
.ok_or_else(|| format_err!("keydata attribute is not found")) .context("keydata attribute is not found")
.and_then(|raw| { .and_then(|raw| {
SignedPublicKey::from_base64(&raw) SignedPublicKey::from_base64(&raw).context("autocrypt key cannot be decoded")
.map_err(|_| format_err!("Autocrypt key cannot be decoded"))
}) })
.and_then(|key| { .and_then(|key| {
key.verify() key.verify()
.and(Ok(key)) .and(Ok(key))
.map_err(|_| format_err!("Autocrypt key cannot be verified")) .context("autocrypt key cannot be verified")
})?; })?;
let prefer_encrypt = attributes let prefer_encrypt = attributes

View File

@@ -620,7 +620,10 @@ impl ChatId {
/// Returns `true`, if message was deleted, `false` otherwise. /// Returns `true`, if message was deleted, `false` otherwise.
async fn maybe_delete_draft(self, context: &Context) -> Result<bool> { async fn maybe_delete_draft(self, context: &Context) -> Result<bool> {
match self.get_draft_msg_id(context).await? { match self.get_draft_msg_id(context).await? {
Some(msg_id) => Ok(msg_id.delete_from_db(context).await.is_ok()), Some(msg_id) => {
msg_id.delete_from_db(context).await?;
Ok(true)
}
None => Ok(false), None => Ok(false),
} }
} }
@@ -640,7 +643,7 @@ impl ChatId {
.param .param
.get_blob(Param::File, context, !msg.is_increation()) .get_blob(Param::File, context, !msg.is_increation())
.await? .await?
.ok_or_else(|| format_err!("No file stored in params"))?; .context("no file stored in params")?;
msg.param.set(Param::File, blob.as_name()); msg.param.set(Param::File, blob.as_name());
} }
} }
@@ -1804,9 +1807,7 @@ async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<()> {
.param .param
.get_blob(Param::File, context, !msg.is_increation()) .get_blob(Param::File, context, !msg.is_increation())
.await? .await?
.ok_or_else(|| { .with_context(|| format!("attachment missing for message of type #{}", msg.viewtype))?;
format_err!("Attachment missing for message of type #{}", msg.viewtype)
})?;
if msg.viewtype == Viewtype::Image { if msg.viewtype == Viewtype::Image {
if let Err(e) = blob.recode_to_image_size(context).await { if let Err(e) = blob.recode_to_image_size(context).await {
@@ -3914,7 +3915,7 @@ mod tests {
assert_eq!(chats.len(), 1); assert_eq!(chats.len(), 1);
// after the device-chat and all messages are deleted, a re-adding should do nothing // after the device-chat and all messages are deleted, a re-adding should do nothing
chats.get_chat_id(0).delete(&t).await.ok(); chats.get_chat_id(0).unwrap().delete(&t).await.ok();
add_device_msg(&t, Some("some-label"), Some(&mut msg)) add_device_msg(&t, Some("some-label"), Some(&mut msg))
.await .await
.ok(); .ok();
@@ -4079,7 +4080,7 @@ mod tests {
.unwrap(); .unwrap();
let mut result = Vec::new(); let mut result = Vec::new();
for chatlist_index in 0..chatlist.len() { for chatlist_index in 0..chatlist.len() {
result.push(chatlist.get_chat_id(chatlist_index)) result.push(chatlist.get_chat_id(chatlist_index).unwrap())
} }
result result
} }
@@ -4502,7 +4503,7 @@ mod tests {
let chats = Chatlist::try_load(&t, 0, None, None).await?; let chats = Chatlist::try_load(&t, 0, None, None).await?;
assert_eq!(chats.len(), 1); assert_eq!(chats.len(), 1);
assert_eq!(chats.get_chat_id(0), chat.id); assert_eq!(chats.get_chat_id(0)?, chat.id);
assert_eq!(chat.id.get_fresh_msg_cnt(&t).await?, 1); assert_eq!(chat.id.get_fresh_msg_cnt(&t).await?, 1);
assert_eq!(t.get_fresh_msgs().await?.len(), 1); assert_eq!(t.get_fresh_msgs().await?.len(), 1);
@@ -4550,7 +4551,7 @@ mod tests {
let chats = Chatlist::try_load(&t, 0, None, None).await?; let chats = Chatlist::try_load(&t, 0, None, None).await?;
assert_eq!(chats.len(), 1); assert_eq!(chats.len(), 1);
let chat_id = chats.get_chat_id(0); let chat_id = chats.get_chat_id(0).unwrap();
assert!(Chat::load_from_db(&t, chat_id) assert!(Chat::load_from_db(&t, chat_id)
.await .await
.unwrap() .unwrap()
@@ -4598,7 +4599,7 @@ mod tests {
let chats = Chatlist::try_load(&t, 0, None, None).await?; let chats = Chatlist::try_load(&t, 0, None, None).await?;
assert_eq!(chats.len(), 1); assert_eq!(chats.len(), 1);
let chat_id = chats.get_chat_id(0); let chat_id = chats.get_chat_id(0)?;
assert!(Chat::load_from_db(&t, chat_id).await?.is_contact_request()); assert!(Chat::load_from_db(&t, chat_id).await?.is_contact_request());
assert_eq!(dc_get_archived_cnt(&t).await?, 0); assert_eq!(dc_get_archived_cnt(&t).await?, 0);
@@ -4607,13 +4608,13 @@ mod tests {
let chats = Chatlist::try_load(&t, 0, None, None).await?; let chats = Chatlist::try_load(&t, 0, None, None).await?;
assert_eq!(chats.len(), 1); assert_eq!(chats.len(), 1);
let chat_id = chats.get_chat_id(0); let chat_id = chats.get_chat_id(0)?;
assert!(chat_id.is_archived_link()); assert!(chat_id.is_archived_link());
assert_eq!(dc_get_archived_cnt(&t).await?, 1); assert_eq!(dc_get_archived_cnt(&t).await?, 1);
let chats = Chatlist::try_load(&t, DC_GCL_ARCHIVED_ONLY, None, None).await?; let chats = Chatlist::try_load(&t, DC_GCL_ARCHIVED_ONLY, None, None).await?;
assert_eq!(chats.len(), 1); assert_eq!(chats.len(), 1);
let chat_id = chats.get_chat_id(0); let chat_id = chats.get_chat_id(0)?;
assert!(Chat::load_from_db(&t, chat_id).await?.is_contact_request()); assert!(Chat::load_from_db(&t, chat_id).await?.is_contact_request());
Ok(()) Ok(())

View File

@@ -1,6 +1,6 @@
//! # Chat list module. //! # Chat list module.
use anyhow::{bail, ensure, Result}; use anyhow::{ensure, Context as _, Result};
use crate::chat::{update_special_chat_names, Chat, ChatId, ChatVisibility}; use crate::chat::{update_special_chat_names, Chat, ChatId, ChatVisibility};
use crate::constants::{ use crate::constants::{
@@ -271,21 +271,23 @@ impl Chatlist {
/// Get a single chat ID of a chatlist. /// Get a single chat ID of a chatlist.
/// ///
/// To get the message object from the message ID, use dc_get_chat(). /// To get the message object from the message ID, use dc_get_chat().
pub fn get_chat_id(&self, index: usize) -> ChatId { pub fn get_chat_id(&self, index: usize) -> Result<ChatId> {
match self.ids.get(index) { let (chat_id, _msg_id) = self
Some((chat_id, _msg_id)) => *chat_id, .ids
None => ChatId::new(0), .get(index)
} .context("chatlist index is out of range")?;
Ok(*chat_id)
} }
/// Get a single message ID of a chatlist. /// Get a single message ID of a chatlist.
/// ///
/// To get the message object from the message ID, use dc_get_msg(). /// To get the message object from the message ID, use dc_get_msg().
pub fn get_msg_id(&self, index: usize) -> Result<Option<MsgId>> { pub fn get_msg_id(&self, index: usize) -> Result<Option<MsgId>> {
match self.ids.get(index) { let (_chat_id, msg_id) = self
Some((_chat_id, msg_id)) => Ok(*msg_id), .ids
None => bail!("Chatlist index out of range"), .get(index)
} .context("chatlist index is out of range")?;
Ok(*msg_id)
} }
/// Returns a summary for a given chatlist index. /// Returns a summary for a given chatlist index.
@@ -299,11 +301,10 @@ impl Chatlist {
// This is because we may want to display drafts here or stuff as // This is because we may want to display drafts here or stuff as
// "is typing". // "is typing".
// Also, sth. as "No messages" would not work if the summary comes from a message. // Also, sth. as "No messages" would not work if the summary comes from a message.
let (chat_id, lastmsg_id) = match self.ids.get(index) { let (chat_id, lastmsg_id) = self
Some(ids) => ids, .ids
None => bail!("Chatlist index out of range"), .get(index)
}; .context("chatlist index is out of range")?;
Chatlist::get_summary2(context, *chat_id, *lastmsg_id, chat).await Chatlist::get_summary2(context, *chat_id, *lastmsg_id, chat).await
} }
@@ -395,9 +396,9 @@ mod tests {
// check that the chatlist starts with the most recent message // check that the chatlist starts with the most recent message
let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap(); let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
assert_eq!(chats.len(), 3); assert_eq!(chats.len(), 3);
assert_eq!(chats.get_chat_id(0), chat_id3); assert_eq!(chats.get_chat_id(0).unwrap(), chat_id3);
assert_eq!(chats.get_chat_id(1), chat_id2); assert_eq!(chats.get_chat_id(1).unwrap(), chat_id2);
assert_eq!(chats.get_chat_id(2), chat_id1); assert_eq!(chats.get_chat_id(2).unwrap(), chat_id1);
// New drafts are sorted to the top // New drafts are sorted to the top
// We have to set a draft on the other two messages, too, as // We have to set a draft on the other two messages, too, as
@@ -414,7 +415,7 @@ mod tests {
} }
let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap(); let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
assert_eq!(chats.get_chat_id(0), chat_id2); assert_eq!(chats.get_chat_id(0).unwrap(), chat_id2);
// check chatlist query and archive functionality // check chatlist query and archive functionality
let chats = Chatlist::try_load(&t, 0, Some("b"), None).await.unwrap(); let chats = Chatlist::try_load(&t, 0, Some("b"), None).await.unwrap();
@@ -445,7 +446,7 @@ mod tests {
let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap(); let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
assert!(chats.len() == 3); assert!(chats.len() == 3);
assert!(!Chat::load_from_db(&t, chats.get_chat_id(0)) assert!(!Chat::load_from_db(&t, chats.get_chat_id(0).unwrap())
.await .await
.unwrap() .unwrap()
.is_self_talk()); .is_self_talk());
@@ -454,7 +455,7 @@ mod tests {
.await .await
.unwrap(); .unwrap();
assert!(chats.len() == 2); // device chat cannot be written and is skipped on forwarding assert!(chats.len() == 2); // device chat cannot be written and is skipped on forwarding
assert!(Chat::load_from_db(&t, chats.get_chat_id(0)) assert!(Chat::load_from_db(&t, chats.get_chat_id(0).unwrap())
.await .await
.unwrap() .unwrap()
.is_self_talk()); .is_self_talk());
@@ -527,7 +528,7 @@ mod tests {
// check, the one-to-one-chat can be found using chatlist search query // check, the one-to-one-chat can be found using chatlist search query
let chats = Chatlist::try_load(&t, 0, Some("bob authname"), None).await?; let chats = Chatlist::try_load(&t, 0, Some("bob authname"), None).await?;
assert_eq!(chats.len(), 1); assert_eq!(chats.len(), 1);
assert_eq!(chats.get_chat_id(0), chat_id); assert_eq!(chats.get_chat_id(0).unwrap(), chat_id);
// change the name of the contact; this also changes the name of the one-to-one-chat // change the name of the contact; this also changes the name of the one-to-one-chat
let test_id = Contact::create(&t, "Bob Nickname", "bob@example.org").await?; let test_id = Contact::create(&t, "Bob Nickname", "bob@example.org").await?;
@@ -583,7 +584,7 @@ mod tests {
// check, the one-to-one-chat can be found using chatlist search query // check, the one-to-one-chat can be found using chatlist search query
let chats = Chatlist::try_load(&t, 0, Some("bob@example.org"), None).await?; let chats = Chatlist::try_load(&t, 0, Some("bob@example.org"), None).await?;
assert_eq!(chats.len(), 1); assert_eq!(chats.len(), 1);
assert_eq!(chats.get_chat_id(0), chat_id); assert_eq!(chats.get_chat_id(0)?, chat_id);
// change the name of the contact; this also changes the name of the one-to-one-chat // change the name of the contact; this also changes the name of the one-to-one-chat
let test_id = Contact::create(&t, "Bob Nickname", "bob@example.org").await?; let test_id = Contact::create(&t, "Bob Nickname", "bob@example.org").await?;
@@ -594,7 +595,7 @@ mod tests {
assert_eq!(chats.len(), 0); // email-addresses are searchable in contacts, not in chats assert_eq!(chats.len(), 0); // email-addresses are searchable in contacts, not in chats
let chats = Chatlist::try_load(&t, 0, Some("Bob Nickname"), None).await?; let chats = Chatlist::try_load(&t, 0, Some("Bob Nickname"), None).await?;
assert_eq!(chats.len(), 1); assert_eq!(chats.len(), 1);
assert_eq!(chats.get_chat_id(0), chat_id); assert_eq!(chats.get_chat_id(0)?, chat_id);
// revert name change, this again changes the name of the one-to-one-chat to the email-address // revert name change, this again changes the name of the one-to-one-chat to the email-address
let test_id = Contact::create(&t, "", "bob@example.org").await?; let test_id = Contact::create(&t, "", "bob@example.org").await?;

View File

@@ -5,7 +5,7 @@ use std::ffi::OsString;
use std::ops::Deref; use std::ops::Deref;
use std::time::{Instant, SystemTime}; use std::time::{Instant, SystemTime};
use anyhow::{bail, ensure, Result}; use anyhow::{bail, ensure, Context as _, Result};
use async_std::{ use async_std::{
channel::{self, Receiver, Sender}, channel::{self, Receiver, Sender},
path::{Path, PathBuf}, path::{Path, PathBuf},
@@ -155,7 +155,10 @@ impl Context {
let ctx = Context { let ctx = Context {
inner: Arc::new(inner), inner: Arc::new(inner),
}; };
ctx.sql.open(&ctx, &ctx.dbfile, false).await?; ctx.sql
.open(&ctx, &ctx.dbfile, false)
.await
.context("failed to open SQL database")?;
Ok(ctx) Ok(ctx)
} }

View File

@@ -209,7 +209,7 @@ pub(crate) async fn dc_receive_imf_inner(
prevent_rename, prevent_rename,
) )
.await .await
.map_err(|err| err.context("add_parts error"))?; .context("add_parts error")?;
if from_id > DC_CONTACT_ID_LAST_SPECIAL { if from_id > DC_CONTACT_ID_LAST_SPECIAL {
contact::update_last_seen(context, from_id, sent_timestamp).await?; contact::update_last_seen(context, from_id, sent_timestamp).await?;
@@ -1845,11 +1845,11 @@ async fn create_or_lookup_mailinglist(
param, param,
) )
.await .await
.map_err(|err| { .with_context(|| {
err.context(format!( format!(
"Failed to create mailinglist '{}' for grpid={}", "Failed to create mailinglist '{}' for grpid={}",
&name, &listid &name, &listid
)) )
})?; })?;
chat::add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF).await?; chat::add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF).await?;
@@ -2505,7 +2505,7 @@ mod tests {
dc_receive_imf(&t, MSGRMSG, "INBOX", false).await.unwrap(); dc_receive_imf(&t, MSGRMSG, "INBOX", false).await.unwrap();
let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap(); let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
assert_eq!(chats.len(), 1); assert_eq!(chats.len(), 1);
let chat_id = chats.get_chat_id(0); let chat_id = chats.get_chat_id(0).unwrap();
assert!(!chat_id.is_special()); assert!(!chat_id.is_special());
let chat = chat::Chat::load_from_db(&t, chat_id).await.unwrap(); let chat = chat::Chat::load_from_db(&t, chat_id).await.unwrap();
assert!(chat.is_contact_request()); assert!(chat.is_contact_request());
@@ -2539,7 +2539,7 @@ mod tests {
dc_receive_imf(&t, GRP_MAIL, "INBOX", false).await.unwrap(); dc_receive_imf(&t, GRP_MAIL, "INBOX", false).await.unwrap();
let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap(); let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
assert_eq!(chats.len(), 2); assert_eq!(chats.len(), 2);
let chat_id = chats.get_chat_id(0); let chat_id = chats.get_chat_id(0).unwrap();
let chat = chat::Chat::load_from_db(&t, chat_id).await.unwrap(); let chat = chat::Chat::load_from_db(&t, chat_id).await.unwrap();
assert_eq!(chat.typ, Chattype::Group); assert_eq!(chat.typ, Chattype::Group);
assert_eq!(chat.name, "group with Alice, Bob and Claire"); assert_eq!(chat.name, "group with Alice, Bob and Claire");
@@ -2555,7 +2555,7 @@ mod tests {
// adhoc-group with unknown contacts with show_emails=all will show up in a single chat // adhoc-group with unknown contacts with show_emails=all will show up in a single chat
let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap(); let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
assert_eq!(chats.len(), 1); assert_eq!(chats.len(), 1);
let chat_id = chats.get_chat_id(0); let chat_id = chats.get_chat_id(0).unwrap();
let chat = chat::Chat::load_from_db(&t, chat_id).await.unwrap(); let chat = chat::Chat::load_from_db(&t, chat_id).await.unwrap();
assert!(chat.is_contact_request()); assert!(chat.is_contact_request());
chat_id.accept(&t).await.unwrap(); chat_id.accept(&t).await.unwrap();
@@ -3078,7 +3078,7 @@ mod tests {
let chats = Chatlist::try_load(&t.ctx, 0, None, None).await?; let chats = Chatlist::try_load(&t.ctx, 0, None, None).await?;
assert_eq!(chats.len(), 1); assert_eq!(chats.len(), 1);
let chat_id = chats.get_chat_id(0); let chat_id = chats.get_chat_id(0).unwrap();
chat_id.accept(&t).await.unwrap(); chat_id.accept(&t).await.unwrap();
let chat = chat::Chat::load_from_db(&t.ctx, chat_id).await?; let chat = chat::Chat::load_from_db(&t.ctx, chat_id).await?;
@@ -3146,7 +3146,7 @@ mod tests {
.await .await
.unwrap(); .unwrap();
let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap();
let chat_id = chats.get_chat_id(0); let chat_id = chats.get_chat_id(0).unwrap();
chat_id.accept(&t).await.unwrap(); chat_id.accept(&t).await.unwrap();
let chat = Chat::load_from_db(&t.ctx, chat_id).await.unwrap(); let chat = Chat::load_from_db(&t.ctx, chat_id).await.unwrap();
assert_eq!(chat.name, "delta-dev"); assert_eq!(chat.name, "delta-dev");
@@ -3249,7 +3249,7 @@ Hello mailinglist!\r\n"
.unwrap(); .unwrap();
let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap();
assert_eq!(chats.len(), 1); assert_eq!(chats.len(), 1);
let chat_id = chats.get_chat_id(0); let chat_id = chats.get_chat_id(0).unwrap();
let chat = Chat::load_from_db(&t.ctx, chat_id).await.unwrap(); let chat = Chat::load_from_db(&t.ctx, chat_id).await.unwrap();
assert!(chat.is_contact_request()); assert!(chat.is_contact_request());

View File

@@ -1164,7 +1164,7 @@ Hop: From: hq5.example.org; By: hq5.example.org; Date: Mon, 27 Dec 2021 11:21:22
maybe_warn_on_bad_time(&t, timestamp_past, get_provider_update_timestamp()).await; maybe_warn_on_bad_time(&t, timestamp_past, get_provider_update_timestamp()).await;
let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap(); let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
assert_eq!(chats.len(), 1); assert_eq!(chats.len(), 1);
let device_chat_id = chats.get_chat_id(0); let device_chat_id = chats.get_chat_id(0).unwrap();
let msgs = chat::get_chat_msgs(&t, device_chat_id, 0, None) let msgs = chat::get_chat_msgs(&t, device_chat_id, 0, None)
.await .await
.unwrap(); .unwrap();
@@ -1202,7 +1202,7 @@ Hop: From: hq5.example.org; By: hq5.example.org; Date: Mon, 27 Dec 2021 11:21:22
.await; .await;
let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap(); let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
assert_eq!(chats.len(), 1); assert_eq!(chats.len(), 1);
assert_eq!(device_chat_id, chats.get_chat_id(0)); assert_eq!(device_chat_id, chats.get_chat_id(0).unwrap());
let msgs = chat::get_chat_msgs(&t, device_chat_id, 0, None) let msgs = chat::get_chat_msgs(&t, device_chat_id, 0, None)
.await .await
.unwrap(); .unwrap();
@@ -1234,7 +1234,7 @@ Hop: From: hq5.example.org; By: hq5.example.org; Date: Mon, 27 Dec 2021 11:21:22
.await; .await;
let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap(); let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
assert_eq!(chats.len(), 1); assert_eq!(chats.len(), 1);
let device_chat_id = chats.get_chat_id(0); let device_chat_id = chats.get_chat_id(0).unwrap();
let msgs = chat::get_chat_msgs(&t, device_chat_id, 0, None) let msgs = chat::get_chat_msgs(&t, device_chat_id, 0, None)
.await .await
.unwrap(); .unwrap();
@@ -1256,7 +1256,7 @@ Hop: From: hq5.example.org; By: hq5.example.org; Date: Mon, 27 Dec 2021 11:21:22
.await; .await;
let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap(); let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
assert_eq!(chats.len(), 1); assert_eq!(chats.len(), 1);
let device_chat_id = chats.get_chat_id(0); let device_chat_id = chats.get_chat_id(0).unwrap();
let msgs = chat::get_chat_msgs(&t, device_chat_id, 0, None) let msgs = chat::get_chat_msgs(&t, device_chat_id, 0, None)
.await .await
.unwrap(); .unwrap();
@@ -1273,7 +1273,7 @@ Hop: From: hq5.example.org; By: hq5.example.org; Date: Mon, 27 Dec 2021 11:21:22
.await; .await;
let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap(); let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
assert_eq!(chats.len(), 1); assert_eq!(chats.len(), 1);
let device_chat_id = chats.get_chat_id(0); let device_chat_id = chats.get_chat_id(0).unwrap();
let msgs = chat::get_chat_msgs(&t, device_chat_id, 0, None) let msgs = chat::get_chat_msgs(&t, device_chat_id, 0, None)
.await .await
.unwrap(); .unwrap();

View File

@@ -196,9 +196,13 @@ impl Imap {
// we are connected, and the folder is selected // we are connected, and the folder is selected
info!(context, "Downloading message {}/{} fully...", folder, uid); info!(context, "Downloading message {}/{} fully...", folder, uid);
let (last_uid, _received) = self let (last_uid, _received) = match self
.fetch_many_msgs(context, folder, vec![uid], false, false) .fetch_many_msgs(context, folder, vec![uid], false, false)
.await; .await
{
Ok(res) => res,
Err(_) => return ImapActionResult::Failed,
};
if last_uid.is_none() { if last_uid.is_none() {
ImapActionResult::Failed ImapActionResult::Failed
} else { } else {

View File

@@ -2,7 +2,7 @@
use std::collections::HashSet; use std::collections::HashSet;
use anyhow::{bail, format_err, Result}; use anyhow::{bail, format_err, Context as _, Result};
use mailparse::ParsedMail; use mailparse::ParsedMail;
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
@@ -121,9 +121,9 @@ impl EncryptHelper {
.into_iter() .into_iter()
.filter_map(|(state, addr)| state.map(|s| (s, addr))) .filter_map(|(state, addr)| state.map(|s| (s, addr)))
{ {
let key = peerstate.take_key(min_verified).ok_or_else(|| { let key = peerstate
format_err!("proper enc-key for {} missing, cannot encrypt", addr) .take_key(min_verified)
})?; .with_context(|| format!("proper enc-key for {} missing, cannot encrypt", addr))?;
keyring.add(key); keyring.add(key);
} }
keyring.add(self.public_key.clone()); keyring.add(self.public_key.clone());
@@ -390,12 +390,10 @@ pub async fn ensure_secret_key_exists(context: &Context) -> Result<String> {
let self_addr = context let self_addr = context
.get_config(Config::ConfiguredAddr) .get_config(Config::ConfiguredAddr)
.await? .await?
.ok_or_else(|| { .context(concat!(
format_err!(concat!( "Failed to get self address, ",
"Failed to get self address, ", "cannot ensure secret key if not configured."
"cannot ensure secret key if not configured." ))?;
))
})?;
SignedPublicKey::load_self(context).await?; SignedPublicKey::load_self(context).await?;
Ok(self_addr) Ok(self_addr)
} }

View File

@@ -314,33 +314,24 @@ impl Imap {
} }
}; };
let login_res = match connection_res { let client = connection_res?;
Ok(client) => { let config = &self.config;
let config = &self.config; let imap_user: &str = config.lp.user.as_ref();
let imap_user: &str = config.lp.user.as_ref(); let imap_pw: &str = config.lp.password.as_ref();
let imap_pw: &str = config.lp.password.as_ref();
if oauth2 { let login_res = if oauth2 {
let addr: &str = config.addr.as_ref(); let addr: &str = config.addr.as_ref();
if let Some(token) = let token = dc_get_oauth2_access_token(context, addr, imap_pw, true)
dc_get_oauth2_access_token(context, addr, imap_pw, true).await? .await?
{ .context("IMAP could not get OAUTH token")?;
let auth = OAuth2 { let auth = OAuth2 {
user: imap_user.into(), user: imap_user.into(),
access_token: token, access_token: token,
}; };
client.authenticate("XOAUTH2", auth).await client.authenticate("XOAUTH2", auth).await
} else { } else {
bail!("IMAP Could not get OAUTH token"); client.login(imap_user, imap_pw).await
}
} else {
client.login(imap_user, imap_pw).await
}
}
Err(err) => {
bail!(err);
}
}; };
self.should_reconnect = false; self.should_reconnect = false;
@@ -397,24 +388,19 @@ impl Imap {
return Ok(()); return Ok(());
} }
match &mut self.session { let session = self.session.as_mut().context(
Some(ref mut session) => match session.capabilities().await { "Can't determine server capabilities because connection was not established",
Ok(caps) => { )?;
self.config.can_idle = caps.has_str("IDLE"); let caps = session
self.config.can_move = caps.has_str("MOVE"); .capabilities()
self.config.can_check_quota = caps.has_str("QUOTA"); .await
self.config.can_condstore = caps.has_str("CONDSTORE"); .context("CAPABILITY command error")?;
self.capabilities_determined = true; self.config.can_idle = caps.has_str("IDLE");
Ok(()) self.config.can_move = caps.has_str("MOVE");
} self.config.can_check_quota = caps.has_str("QUOTA");
Err(err) => { self.config.can_condstore = caps.has_str("CONDSTORE");
bail!("CAPABILITY command error: {}", err); self.capabilities_determined = true;
} Ok(())
},
None => {
bail!("Can't determine server capabilities because connection was not established")
}
}
} }
/// Prepare for IMAP operation. /// Prepare for IMAP operation.
@@ -505,29 +491,25 @@ impl Imap {
self.select_folder(context, Some(&folder)).await?; self.select_folder(context, Some(&folder)).await?;
let session = if let Some(ref mut session) = &mut self.session { let session = self
session .session
} else { .as_mut()
bail!("IMAP No Connection established"); .context("IMAP No connection established")?;
};
match session.uid_fetch("1:*", RFC724MID_UID).await { let mut list = session
Ok(mut list) => { .uid_fetch("1:*", RFC724MID_UID)
while let Some(fetch) = list.next().await { .await
let msg = fetch?; .with_context(|| format!("can't resync folder {}", folder))?;
while let Some(fetch) = list.next().await {
let msg = fetch?;
// Get Message-ID // Get Message-ID
let message_id = get_fetch_headers(&msg) let message_id = get_fetch_headers(&msg)
.and_then(|headers| prefetch_get_message_id(&headers)) .and_then(|headers| prefetch_get_message_id(&headers))
.ok(); .ok();
if let (Some(uid), Some(rfc724_mid)) = (msg.uid, message_id) { if let (Some(uid), Some(rfc724_mid)) = (msg.uid, message_id) {
msg_ids.insert(uid, rfc724_mid); msg_ids.insert(uid, rfc724_mid);
}
}
}
Err(err) => {
bail!("Can't resync folder {}: {}", folder, err);
} }
} }
@@ -575,9 +557,11 @@ impl Imap {
) -> Result<bool> { ) -> Result<bool> {
let newly_selected = self.select_or_create_folder(context, folder).await?; let newly_selected = self.select_or_create_folder(context, folder).await?;
let mailbox = &mut self.config.selected_mailbox.as_ref(); let mailbox = self
let mailbox = .config
mailbox.with_context(|| format!("No mailbox selected, folder: {}", folder))?; .selected_mailbox
.as_mut()
.with_context(|| format!("No mailbox selected, folder: {}", folder))?;
let new_uid_validity = mailbox let new_uid_validity = mailbox
.uid_validity .uid_validity
@@ -806,7 +790,7 @@ impl Imap {
false, false,
fetch_existing_msgs, fetch_existing_msgs,
) )
.await; .await?;
let (largest_uid_partially_fetched, received_msgs_2) = self let (largest_uid_partially_fetched, received_msgs_2) = self
.fetch_many_msgs( .fetch_many_msgs(
@@ -816,7 +800,7 @@ impl Imap {
true, true,
fetch_existing_msgs, fetch_existing_msgs,
) )
.await; .await?;
received_msgs.extend(received_msgs_2); received_msgs.extend(received_msgs_2);
// determine which uid_next to use to update to // determine which uid_next to use to update to
@@ -1121,15 +1105,14 @@ impl Imap {
/// Gets the from, to and bcc addresses from all existing outgoing emails. /// Gets the from, to and bcc addresses from all existing outgoing emails.
pub async fn get_all_recipients(&mut self, context: &Context) -> Result<Vec<SingleInfo>> { pub async fn get_all_recipients(&mut self, context: &Context) -> Result<Vec<SingleInfo>> {
if self.session.is_none() { let session = self
bail!("IMAP No Connection established"); .session
} .as_mut()
.context("IMAP No Connection established")?;
let session = self.session.as_mut().unwrap();
let self_addr = context let self_addr = context
.get_config(Config::ConfiguredAddr) .get_config(Config::ConfiguredAddr)
.await? .await?
.ok_or_else(|| format_err!("Not configured"))?; .context("not configured")?;
let search_command = format!("FROM \"{}\"", self_addr); let search_command = format!("FROM \"{}\"", self_addr);
let uids = session let uids = session
@@ -1143,9 +1126,7 @@ impl Imap {
let mut list = session let mut list = session
.uid_fetch(uid_set, "(UID BODY.PEEK[HEADER.FIELDS (FROM TO CC BCC)])") .uid_fetch(uid_set, "(UID BODY.PEEK[HEADER.FIELDS (FROM TO CC BCC)])")
.await .await
.map_err(|err| { .context("IMAP Could not fetch")?;
format_err!("IMAP Could not fetch (get_all_recipients()): {}", err)
})?;
while let Some(fetch) = list.next().await { while let Some(fetch) = list.next().await {
let msg = fetch?; let msg = fetch?;
@@ -1177,7 +1158,7 @@ impl Imap {
let mut list = session let mut list = session
.uid_fetch(set, PREFETCH_FLAGS) .uid_fetch(set, PREFETCH_FLAGS)
.await .await
.map_err(|err| format_err!("IMAP Could not fetch: {}", err))?; .context("IMAP could not fetch")?;
let mut msgs = BTreeMap::new(); let mut msgs = BTreeMap::new();
while let Some(fetch) = list.next().await { while let Some(fetch) = list.next().await {
@@ -1203,13 +1184,14 @@ impl Imap {
/// Like fetch_after(), but not for new messages but existing ones (the DC_FETCH_EXISTING_MSGS_COUNT newest messages) /// Like fetch_after(), but not for new messages but existing ones (the DC_FETCH_EXISTING_MSGS_COUNT newest messages)
async fn prefetch_existing_msgs(&mut self) -> Result<BTreeMap<u32, async_imap::types::Fetch>> { async fn prefetch_existing_msgs(&mut self) -> Result<BTreeMap<u32, async_imap::types::Fetch>> {
let exists: i64 = { let exists: i64 = {
let mailbox = self.config.selected_mailbox.as_ref(); let mailbox = self
let mailbox = mailbox.context("fetch_existing_msgs_prefetch(): no mailbox selected")?; .config
.selected_mailbox
.as_ref()
.context("no mailbox")?;
mailbox.exists.into() mailbox.exists.into()
}; };
let session = self.session.as_mut(); let session = self.session.as_mut().context("no IMAP session")?;
let session =
session.context("fetch_existing_msgs_prefetch(): IMAP No Connection established")?;
// Fetch last DC_FETCH_EXISTING_MSGS_COUNT (100) messages. // Fetch last DC_FETCH_EXISTING_MSGS_COUNT (100) messages.
// Sequence numbers are sequential. If there are 1000 messages in the inbox, // Sequence numbers are sequential. If there are 1000 messages in the inbox,
@@ -1219,7 +1201,7 @@ impl Imap {
let mut list = session let mut list = session
.fetch(&set, PREFETCH_FLAGS) .fetch(&set, PREFETCH_FLAGS)
.await .await
.map_err(|err| format_err!("IMAP Could not fetch: {}", err))?; .context("IMAP Could not fetch")?;
let mut msgs = BTreeMap::new(); let mut msgs = BTreeMap::new();
while let Some(fetch) = list.next().await { while let Some(fetch) = list.next().await {
@@ -1242,19 +1224,13 @@ impl Imap {
server_uids: Vec<u32>, server_uids: Vec<u32>,
fetch_partially: bool, fetch_partially: bool,
fetching_existing_messages: bool, fetching_existing_messages: bool,
) -> (Option<u32>, Vec<ReceivedMsg>) { ) -> Result<(Option<u32>, Vec<ReceivedMsg>)> {
let mut received_msgs = Vec::new(); let mut received_msgs = Vec::new();
if server_uids.is_empty() { if server_uids.is_empty() {
return (None, Vec::new()); return Ok((None, Vec::new()));
} }
let session = match self.session.as_mut() { let session = self.session.as_mut().context("no IMAP session")?;
Some(session) => session,
None => {
warn!(context, "Not connected");
return (None, Vec::new());
}
};
let sets = build_sequence_sets(server_uids.clone()); let sets = build_sequence_sets(server_uids.clone());
let mut count = 0; let mut count = 0;
@@ -1277,14 +1253,12 @@ impl Imap {
// TODO: maybe differentiate between IO and input/parsing problems // TODO: maybe differentiate between IO and input/parsing problems
// so we don't reconnect if we have a (rare) input/output parsing problem? // so we don't reconnect if we have a (rare) input/output parsing problem?
self.should_reconnect = true; self.should_reconnect = true;
warn!( bail!(
context,
"Error on fetching messages #{} from folder \"{}\"; error={}.", "Error on fetching messages #{} from folder \"{}\"; error={}.",
&set, &set,
folder, folder,
err err
); );
return (None, Vec::new());
} }
}; };
@@ -1363,50 +1337,34 @@ impl Imap {
); );
} }
(last_uid, received_msgs) Ok((last_uid, received_msgs))
} }
async fn add_flag_finalized(&mut self, context: &Context, server_uid: u32, flag: &str) -> bool { /// Returns success if we successfully set the flag or we otherwise
// return true if we successfully set the flag or we otherwise /// think add_flag should not be retried: Disconnection during setting
// think add_flag should not be retried: Disconnection during setting /// the flag, or other imap-errors, returns true as well.
// the flag, or other imap-errors, returns true as well. ///
// /// Returning error means that the operation can be retried.
// returning false means that the operation can be retried. async fn add_flag_finalized(&mut self, server_uid: u32, flag: &str) -> Result<()> {
if server_uid == 0 {
return true; // might be moved but we don't want to have a stuck job
}
let s = server_uid.to_string(); let s = server_uid.to_string();
self.add_flag_finalized_with_set(context, &s, flag).await self.add_flag_finalized_with_set(&s, flag).await
} }
async fn add_flag_finalized_with_set( async fn add_flag_finalized_with_set(&mut self, uid_set: &str, flag: &str) -> Result<()> {
&mut self,
context: &Context,
uid_set: &str,
flag: &str,
) -> bool {
if self.should_reconnect() { if self.should_reconnect() {
return false; bail!("Can't set flag, should reconnect");
} }
if let Some(ref mut session) = &mut self.session {
let query = format!("+FLAGS ({})", flag); let session = self.session.as_mut().context("No session").unwrap();
match session.uid_store(uid_set, &query).await { let query = format!("+FLAGS ({})", flag);
Ok(mut responses) => { let mut responses = session
while let Some(_response) = responses.next().await { .uid_store(uid_set, &query)
// Read all the responses .await
} .with_context(|| format!("IMAP failed to store: ({}, {})", uid_set, query))?;
} while let Some(_response) = responses.next().await {
Err(err) => { // Read all the responses
warn!(
context,
"IMAP failed to store: ({}, {}) {:?}", uid_set, query, err
);
}
}
true // we tried once, that's probably enough for setting flag
} else {
unreachable!();
} }
Ok(())
} }
pub async fn prepare_imap_operation_on_msg( pub async fn prepare_imap_operation_on_msg(
@@ -1449,7 +1407,7 @@ impl Imap {
} }
} }
pub async fn set_seen( pub(crate) async fn set_seen(
&mut self, &mut self,
context: &Context, context: &Context,
folder: &str, folder: &str,
@@ -1464,14 +1422,14 @@ impl Imap {
// we are connected, and the folder is selected // we are connected, and the folder is selected
info!(context, "Marking message {}/{} as seen...", folder, uid,); info!(context, "Marking message {}/{} as seen...", folder, uid,);
if self.add_flag_finalized(context, uid, "\\Seen").await { if let Err(err) = self.add_flag_finalized(uid, "\\Seen").await {
ImapActionResult::Success
} else {
warn!( warn!(
context, context,
"Cannot mark message {} in folder {} as seen, ignoring.", uid, folder "Cannot mark message {} in folder {} as seen, ignoring: {}.", uid, folder, err
); );
ImapActionResult::Failed ImapActionResult::Failed
} else {
ImapActionResult::Success
} }
} }
@@ -1492,10 +1450,10 @@ impl Imap {
let display_imap_id = format!("{}/{}", folder, uid); let display_imap_id = format!("{}/{}", folder, uid);
// mark the message for deletion // mark the message for deletion
if !self.add_flag_finalized(context, uid, "\\Deleted").await { if let Err(err) = self.add_flag_finalized(uid, "\\Deleted").await {
warn!( warn!(
context, context,
"Cannot mark message {} as \"Deleted\".", display_imap_id "Cannot mark message {} as \"Deleted\": {}.", display_imap_id, err
); );
ImapActionResult::RetryLater ImapActionResult::RetryLater
} else { } else {
@@ -1522,18 +1480,15 @@ impl Imap {
} }
pub async fn configure_folders(&mut self, context: &Context, create_mvbox: bool) -> Result<()> { pub async fn configure_folders(&mut self, context: &Context, create_mvbox: bool) -> Result<()> {
let session = match self.session { let session = self
Some(ref mut session) => session, .session
None => bail!("no IMAP connection established"), .as_mut()
}; .context("no IMAP connection established")?;
let mut folders = match session.list(Some(""), Some("*")).await {
Ok(f) => f,
Err(err) => {
bail!("list_folders failed: {}", err);
}
};
let mut folders = session
.list(Some(""), Some("*"))
.await
.context("list_folders failed")?;
let mut delimiter = ".".to_string(); let mut delimiter = ".".to_string();
let mut delimiter_is_default = true; let mut delimiter_is_default = true;
let mut mvbox_folder = None; let mut mvbox_folder = None;
@@ -1638,11 +1593,8 @@ impl Imap {
/// Drains all responses from `session.unsolicited_responses` in the process. /// Drains all responses from `session.unsolicited_responses` in the process.
/// If this returns `true`, this means that new emails arrived and you should /// If this returns `true`, this means that new emails arrived and you should
/// fetch again, even if you just fetched. /// fetch again, even if you just fetched.
fn server_sent_unsolicited_exists(&self, context: &Context) -> bool { fn server_sent_unsolicited_exists(&self, context: &Context) -> Result<bool> {
let session = match &self.session { let session = self.session.as_ref().context("no session")?;
Some(s) => s,
None => return false,
};
let mut unsolicited_exists = false; let mut unsolicited_exists = false;
while let Ok(response) = session.unsolicited_responses.try_recv() { while let Ok(response) = session.unsolicited_responses.try_recv() {
match response { match response {
@@ -1656,7 +1608,7 @@ impl Imap {
_ => info!(context, "ignoring unsolicited response {:?}", response), _ => info!(context, "ignoring unsolicited response {:?}", response),
} }
} }
unsolicited_exists Ok(unsolicited_exists)
} }
pub fn can_check_quota(&self) -> bool { pub fn can_check_quota(&self) -> bool {

View File

@@ -1,6 +1,6 @@
use super::Imap; use super::Imap;
use anyhow::{bail, format_err, Result}; use anyhow::{bail, Context as _, Result};
use async_imap::extensions::idle::IdleResponse; use async_imap::extensions::idle::IdleResponse;
use async_std::prelude::*; use async_std::prelude::*;
use std::time::{Duration, SystemTime}; use std::time::{Duration, SystemTime};
@@ -31,7 +31,7 @@ impl Imap {
let timeout = Duration::from_secs(23 * 60); let timeout = Duration::from_secs(23 * 60);
let mut info = Default::default(); let mut info = Default::default();
if self.server_sent_unsolicited_exists(context) { if self.server_sent_unsolicited_exists(context)? {
return Ok(info); return Ok(info);
} }
@@ -90,8 +90,8 @@ impl Imap {
let session = handle let session = handle
.done() .done()
.timeout(Duration::from_secs(15)) .timeout(Duration::from_secs(15))
.await .await?
.map_err(|err| format_err!("IMAP IDLE protocol timed out: {}", err))??; .context("IMAP IDLE protocol timed out")?;
self.session = Some(Session { inner: session }); self.session = Some(Session { inner: session });
} else { } else {
warn!(context, "Attempted to idle without a session"); warn!(context, "Attempted to idle without a session");

View File

@@ -71,7 +71,7 @@ impl Imap {
// Don't scan folders that are watched anyway // Don't scan folders that are watched anyway
if !watched_folders.contains(&folder.name().to_string()) && !is_drafts { if !watched_folders.contains(&folder.name().to_string()) && !is_drafts {
// Drain leftover unsolicited EXISTS messages // Drain leftover unsolicited EXISTS messages
self.server_sent_unsolicited_exists(context); self.server_sent_unsolicited_exists(context)?;
loop { loop {
self.fetch_move_delete(context, folder.name()) self.fetch_move_delete(context, folder.name())
@@ -79,7 +79,7 @@ impl Imap {
.ok_or_log_msg(context, "Can't fetch new msgs in scanned folder"); .ok_or_log_msg(context, "Can't fetch new msgs in scanned folder");
// If the server sent an unsocicited EXISTS during the fetch, we need to fetch again // If the server sent an unsocicited EXISTS during the fetch, we need to fetch again
if !self.server_sent_unsolicited_exists(context) { if !self.server_sent_unsolicited_exists(context)? {
break; break;
} }
} }

View File

@@ -372,13 +372,13 @@ impl Job {
let filename = job_try!(job_try!(self let filename = job_try!(job_try!(self
.param .param
.get_path(Param::File, context) .get_path(Param::File, context)
.map_err(|_| format_err!("Can't get filename"))) .context("can't get filename"))
.ok_or_else(|| format_err!("Can't get filename"))); .context("Can't get filename"));
let body = job_try!(dc_read_file(context, &filename).await); let body = job_try!(dc_read_file(context, &filename).await);
let recipients = job_try!(self.param.get(Param::Recipients).ok_or_else(|| { let recipients = job_try!(self
warn!(context, "Missing recipients for job {}", self.job_id); .param
format_err!("Missing recipients") .get(Param::Recipients)
})); .context("missing recipients"));
let recipients_list = recipients let recipients_list = recipients
.split('\x1e') .split('\x1e')

View File

@@ -4,7 +4,7 @@ use std::collections::BTreeMap;
use std::fmt; use std::fmt;
use std::io::Cursor; use std::io::Cursor;
use anyhow::{format_err, Result}; use anyhow::{format_err, Context as _, Result};
use async_trait::async_trait; use async_trait::async_trait;
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
use pgp::composed::Deserializable; use pgp::composed::Deserializable;
@@ -50,8 +50,7 @@ pub trait DcKey: Serialize + Deserializable + KeyTrait + Clone {
/// the ASCII-armored representation. /// the ASCII-armored representation.
fn from_asc(data: &str) -> Result<(Self::KeyType, BTreeMap<String, String>)> { fn from_asc(data: &str) -> Result<(Self::KeyType, BTreeMap<String, String>)> {
let bytes = data.as_bytes(); let bytes = data.as_bytes();
Self::KeyType::from_armor_single(Cursor::new(bytes)) Self::KeyType::from_armor_single(Cursor::new(bytes)).context("rPGP error")
.map_err(|err| format_err!("rPGP error: {}", err))
} }
/// Load the users' default key from the database. /// Load the users' default key from the database.
@@ -202,7 +201,7 @@ async fn generate_keypair(context: &Context) -> Result<KeyPair> {
let addr = context let addr = context
.get_config(Config::ConfiguredAddr) .get_config(Config::ConfiguredAddr)
.await? .await?
.ok_or_else(|| format_err!("No address configured"))?; .context("no address configured")?;
let addr = EmailAddress::new(&addr)?; let addr = EmailAddress::new(&addr)?;
let _guard = context.generating_key_mutex.lock().await; let _guard = context.generating_key_mutex.lock().await;
@@ -289,13 +288,13 @@ pub async fn store_self_keypair(
paramsv![public_key, secret_key], paramsv![public_key, secret_key],
) )
.await .await
.map_err(|err| err.context("failed to remove old use of key"))?; .context("failed to remove old use of key")?;
if default == KeyPairUse::Default { if default == KeyPairUse::Default {
context context
.sql .sql
.execute("UPDATE keypairs SET is_default=0;", paramsv![]) .execute("UPDATE keypairs SET is_default=0;", paramsv![])
.await .await
.map_err(|err| err.context("failed to clear default"))?; .context("failed to clear default")?;
} }
let is_default = match default { let is_default = match default {
KeyPairUse::Default => true as i32, KeyPairUse::Default => true as i32,
@@ -313,7 +312,7 @@ pub async fn store_self_keypair(
paramsv![addr, is_default, public_key, secret_key, t], paramsv![addr, is_default, public_key, secret_key, t],
) )
.await .await
.map_err(|err| err.context("failed to insert keypair"))?; .context("failed to insert keypair")?;
Ok(()) Ok(())
} }

View File

@@ -1534,9 +1534,7 @@ async fn ndn_maybe_add_info_msg(
let contact_id = let contact_id =
Contact::lookup_id_by_addr(context, failed_recipient, Origin::Unknown) Contact::lookup_id_by_addr(context, failed_recipient, Origin::Unknown)
.await? .await?
.ok_or_else(|| { .context("contact ID not found")?;
format_err!("ndn_maybe_add_info_msg: Contact ID not found")
})?;
let contact = Contact::load_from_db(context, contact_id).await?; let contact = Contact::load_from_db(context, contact_id).await?;
// Tell the user which of the recipients failed if we know that (because in // Tell the user which of the recipients failed if we know that (because in
// a group, this might otherwise be unclear) // a group, this might otherwise be unclear)
@@ -1988,9 +1986,9 @@ mod tests {
let msg2 = alice.get_last_msg().await; let msg2 = alice.get_last_msg().await;
let chats = Chatlist::try_load(&alice, 0, None, None).await?; let chats = Chatlist::try_load(&alice, 0, None, None).await?;
assert_eq!(chats.len(), 1); assert_eq!(chats.len(), 1);
assert_eq!(chats.get_chat_id(0), alice_chat.id); assert_eq!(chats.get_chat_id(0)?, alice_chat.id);
assert_eq!(chats.get_chat_id(0), msg1.chat_id); assert_eq!(chats.get_chat_id(0)?, msg1.chat_id);
assert_eq!(chats.get_chat_id(0), msg2.chat_id); assert_eq!(chats.get_chat_id(0)?, msg2.chat_id);
assert_eq!(alice_chat.id.get_fresh_msg_cnt(&alice).await?, 2); assert_eq!(alice_chat.id.get_fresh_msg_cnt(&alice).await?, 2);
assert_eq!(alice.get_fresh_msgs().await?.len(), 2); assert_eq!(alice.get_fresh_msgs().await?.len(), 2);

View File

@@ -2,7 +2,7 @@
use std::convert::TryInto; use std::convert::TryInto;
use anyhow::{bail, ensure, format_err, Context as _, Result}; use anyhow::{bail, ensure, Context as _, Result};
use chrono::TimeZone; use chrono::TimeZone;
use lettre_email::{mime, Address, Header, MimeMultipartType, PartBuilder}; use lettre_email::{mime, Address, Header, MimeMultipartType, PartBuilder};
@@ -281,7 +281,7 @@ impl<'a> MimeFactory<'a> {
let self_addr = context let self_addr = context
.get_config(Config::ConfiguredAddr) .get_config(Config::ConfiguredAddr)
.await? .await?
.ok_or_else(|| format_err!("Not configured"))?; .context("not configured")?;
let mut res = Vec::new(); let mut res = Vec::new();
for (_, addr) in self for (_, addr) in self
@@ -1283,7 +1283,7 @@ async fn build_body_file(
.param .param
.get_blob(Param::File, context, true) .get_blob(Param::File, context, true)
.await? .await?
.ok_or_else(|| format_err!("msg has no filename"))?; .context("msg has no filename")?;
let suffix = blob.suffix().unwrap_or("dat"); let suffix = blob.suffix().unwrap_or("dat");
// Get file name to use for sending. For privacy purposes, we do // Get file name to use for sending. For privacy purposes, we do
@@ -1875,7 +1875,7 @@ mod tests {
let chats = Chatlist::try_load(context, 0, None, None).await.unwrap(); let chats = Chatlist::try_load(context, 0, None, None).await.unwrap();
let chat_id = chats.get_chat_id(0); let chat_id = chats.get_chat_id(0).unwrap();
chat_id.accept(context).await.unwrap(); chat_id.accept(context).await.unwrap();
let mut new_msg = Message::new(Viewtype::Text); let mut new_msg = Message::new(Viewtype::Text);
@@ -2058,11 +2058,11 @@ mod tests {
let to = parsed let to = parsed
.headers .headers
.get_first_header("To") .get_first_header("To")
.ok_or_else(|| format_err!("No To: header parsed"))?; .context("no To: header parsed")?;
let to = addrparse_header(to)?; let to = addrparse_header(to)?;
let mailbox = to let mailbox = to
.extract_single_info() .extract_single_info()
.ok_or_else(|| format_err!("To: field does not contain exactly one address"))?; .context("to: field does not contain exactly one address")?;
assert_eq!(mailbox.addr, "bob@example.net"); assert_eq!(mailbox.addr, "bob@example.net");
Ok(()) Ok(())

View File

@@ -4,7 +4,7 @@ use std::collections::{BTreeMap, HashSet};
use std::io; use std::io;
use std::io::Cursor; use std::io::Cursor;
use anyhow::{bail, ensure, format_err, Result}; use anyhow::{bail, ensure, format_err, Context as _, Result};
use pgp::armor::BlockType; use pgp::armor::BlockType;
use pgp::composed::{ use pgp::composed::{
Deserializable, KeyType as PgpKeyType, Message, SecretKeyParamsBuilder, SignedPublicKey, Deserializable, KeyType as PgpKeyType, Message, SecretKeyParamsBuilder, SignedPublicKey,
@@ -346,7 +346,7 @@ pub async fn pk_validate(
// OpenPGP signature calculation. // OpenPGP signature calculation.
let content = content let content = content
.get(..content.len().saturating_sub(2)) .get(..content.len().saturating_sub(2))
.ok_or_else(|| format_err!("index is out of range"))?; .context("index is out of range")?;
for pkey in pkeys { for pkey in pkeys {
if standalone_signature.verify(pkey, content).is_ok() { if standalone_signature.verify(pkey, content).is_ok() {
@@ -520,7 +520,6 @@ mod tests {
&sig_check_keyring, &sig_check_keyring,
) )
.await .await
.map_err(|err| println!("{:?}", err))
.unwrap(); .unwrap();
assert_eq!(plain, CLEARTEXT); assert_eq!(plain, CLEARTEXT);
assert_eq!(valid_signatures.len(), 1); assert_eq!(valid_signatures.len(), 1);
@@ -536,7 +535,6 @@ mod tests {
&sig_check_keyring, &sig_check_keyring,
) )
.await .await
.map_err(|err| println!("{:?}", err))
.unwrap(); .unwrap();
assert_eq!(plain, CLEARTEXT); assert_eq!(plain, CLEARTEXT);
assert_eq!(valid_signatures.len(), 1); assert_eq!(valid_signatures.len(), 1);

View File

@@ -304,14 +304,14 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
fn decode_account(qr: &str) -> Result<Qr> { fn decode_account(qr: &str) -> Result<Qr> {
let payload = qr let payload = qr
.get(DCACCOUNT_SCHEME.len()..) .get(DCACCOUNT_SCHEME.len()..)
.ok_or_else(|| format_err!("Invalid DCACCOUNT payload"))?; .context("invalid DCACCOUNT payload")?;
let url = let url =
url::Url::parse(payload).with_context(|| format!("Invalid account URL: {:?}", payload))?; url::Url::parse(payload).with_context(|| format!("Invalid account URL: {:?}", payload))?;
if url.scheme() == "http" || url.scheme() == "https" { if url.scheme() == "http" || url.scheme() == "https" {
Ok(Qr::Account { Ok(Qr::Account {
domain: url domain: url
.host_str() .host_str()
.ok_or_else(|| format_err!("Can't extract WebRTC instance domain"))? .context("can't extract WebRTC instance domain")?
.to_string(), .to_string(),
}) })
} else { } else {
@@ -323,7 +323,7 @@ fn decode_account(qr: &str) -> Result<Qr> {
fn decode_webrtc_instance(_context: &Context, qr: &str) -> Result<Qr> { fn decode_webrtc_instance(_context: &Context, qr: &str) -> Result<Qr> {
let payload = qr let payload = qr
.get(DCWEBRTC_SCHEME.len()..) .get(DCWEBRTC_SCHEME.len()..)
.ok_or_else(|| format_err!("Invalid DCWEBRTC payload"))?; .context("invalid DCWEBRTC payload")?;
let (_type, url) = Message::parse_webrtc_instance(payload); let (_type, url) = Message::parse_webrtc_instance(payload);
let url = let url =
@@ -333,7 +333,7 @@ fn decode_webrtc_instance(_context: &Context, qr: &str) -> Result<Qr> {
Ok(Qr::WebrtcInstance { Ok(Qr::WebrtcInstance {
domain: url domain: url
.host_str() .host_str()
.ok_or_else(|| format_err!("Can't extract WebRTC instance domain"))? .context("can't extract WebRTC instance domain")?
.to_string(), .to_string(),
instance_pattern: payload.to_string(), instance_pattern: payload.to_string(),
}) })

View File

@@ -1,6 +1,6 @@
//! # Support for IMAP QUOTA extension. //! # Support for IMAP QUOTA extension.
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Context as _, Result};
use async_imap::types::{Quota, QuotaResource}; use async_imap::types::{Quota, QuotaResource};
use std::collections::BTreeMap; use std::collections::BTreeMap;
@@ -64,7 +64,7 @@ async fn get_unique_quota_roots_and_usage(
.iter() .iter()
.find(|q| &q.root_name == quota_root_name) .find(|q| &q.root_name == quota_root_name)
.cloned() .cloned()
.ok_or_else(|| anyhow!("quota_root should have a quota"))?; .context("quota_root should have a quota")?;
// replace old quotas, because between fetching quotaroots for folders, // replace old quotas, because between fetching quotaroots for folders,
// messages could be recieved and so the usage could have been changed // messages could be recieved and so the usage could have been changed
*unique_quota_roots *unique_quota_roots
@@ -96,7 +96,7 @@ fn get_highest_usage<'t>(
} }
} }
highest.ok_or_else(|| anyhow!("no quota_resource found, this is unexpected")) highest.context("no quota_resource found, this is unexpected")
} }
/// Checks if a quota warning is needed. /// Checks if a quota warning is needed.

View File

@@ -7,7 +7,7 @@ use std::collections::HashSet;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::time::Duration; use std::time::Duration;
use anyhow::{bail, format_err, Context as _, Result}; use anyhow::{bail, Context as _, Result};
use async_std::prelude::*; use async_std::prelude::*;
use rusqlite::OpenFlags; use rusqlite::OpenFlags;
@@ -145,7 +145,9 @@ impl Sql {
// rely themselves on the low-level structure. // rely themselves on the low-level structure.
let (recalc_fingerprints, update_icons, disable_server_delete, recode_avatar) = let (recalc_fingerprints, update_icons, disable_server_delete, recode_avatar) =
migrations::run(context, self).await?; migrations::run(context, self)
.await
.context("failed to run migrations")?;
// (2) updates that require high-level objects // (2) updates that require high-level objects
// the structure is complete now and all objects are usable // the structure is complete now and all objects are usable
@@ -261,9 +263,7 @@ impl Sql {
&self, &self,
) -> Result<r2d2::PooledConnection<r2d2_sqlite::SqliteConnectionManager>> { ) -> Result<r2d2::PooledConnection<r2d2_sqlite::SqliteConnectionManager>> {
let lock = self.pool.read().await; let lock = self.pool.read().await;
let pool = lock let pool = lock.as_ref().context("no SQL connection")?;
.as_ref()
.ok_or_else(|| format_err!("No SQL connection"))?;
let conn = pool.get()?; let conn = pool.get()?;
Ok(conn) Ok(conn)

View File

@@ -1,6 +1,6 @@
//! Migrations module. //! Migrations module.
use anyhow::Result; use anyhow::{Context as _, Result};
use crate::config::Config; use crate::config::Config;
use crate::constants::ShowEmails; use crate::constants::ShowEmails;
@@ -19,7 +19,11 @@ pub async fn run(context: &Context, sql: &Sql) -> Result<(bool, bool, bool, bool
let mut exists_before_update = false; let mut exists_before_update = false;
let mut dbversion_before_update = DBVERSION; let mut dbversion_before_update = DBVERSION;
if !sql.table_exists("config").await? { if !sql
.table_exists("config")
.await
.context("failed to check if config table exists")?
{
info!(context, "First time init: creating tables",); info!(context, "First time init: creating tables",);
sql.transaction(move |transaction| { sql.transaction(move |transaction| {
transaction.execute_batch(TABLES)?; transaction.execute_batch(TABLES)?;
@@ -572,7 +576,8 @@ impl Sql {
Ok(()) Ok(())
}) })
.await?; .await
.with_context(|| format!("execute_migration failed for version {}", version))?;
Ok(()) Ok(())
} }

View File

@@ -1280,11 +1280,13 @@ mod tests {
let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap(); let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
assert_eq!(chats.len(), 2); assert_eq!(chats.len(), 2);
let chat0 = Chat::load_from_db(&t, chats.get_chat_id(0)).await.unwrap(); let chat0 = Chat::load_from_db(&t, chats.get_chat_id(0).unwrap())
.await
.unwrap();
let (self_talk_id, device_chat_id) = if chat0.is_self_talk() { let (self_talk_id, device_chat_id) = if chat0.is_self_talk() {
(chats.get_chat_id(0), chats.get_chat_id(1)) (chats.get_chat_id(0).unwrap(), chats.get_chat_id(1).unwrap())
} else { } else {
(chats.get_chat_id(1), chats.get_chat_id(0)) (chats.get_chat_id(1).unwrap(), chats.get_chat_id(0).unwrap())
}; };
// delete self-talk first; this adds a message to device-chat about how self-talk can be restored // delete self-talk first; this adds a message to device-chat about how self-talk can be restored