diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 1d674514a..a02a13f7d 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -2443,7 +2443,14 @@ pub unsafe extern "C" fn dc_chatlist_get_chat_id( return 0; } 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] diff --git a/examples/repl/cmdline.rs b/examples/repl/cmdline.rs index 99c27e1f0..dcdcd9eef 100644 --- a/examples/repl/cmdline.rs +++ b/examples/repl/cmdline.rs @@ -563,7 +563,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu ); 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!( "{}#{}: {} [{} fresh] {}{}{}{}", chat_prefix(&chat), @@ -1142,7 +1142,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu if 0 != i { 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()); } } diff --git a/src/accounts.rs b/src/accounts.rs index 7cc4d3e48..9275549ce 100644 --- a/src/accounts.rs +++ b/src/accounts.rs @@ -56,7 +56,9 @@ impl Accounts { let config_file = dir.join(CONFIG_NAME); 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 emitter = EventEmitter::new(); diff --git a/src/aheader.rs b/src/aheader.rs index 7d886c944..9899c1978 100644 --- a/src/aheader.rs +++ b/src/aheader.rs @@ -2,7 +2,7 @@ //! //! 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::str::FromStr; use std::{fmt, str}; @@ -139,15 +139,14 @@ impl str::FromStr for Aheader { }; let public_key: SignedPublicKey = attributes .remove("keydata") - .ok_or_else(|| format_err!("keydata attribute is not found")) + .context("keydata attribute is not found") .and_then(|raw| { - SignedPublicKey::from_base64(&raw) - .map_err(|_| format_err!("Autocrypt key cannot be decoded")) + SignedPublicKey::from_base64(&raw).context("autocrypt key cannot be decoded") }) .and_then(|key| { key.verify() .and(Ok(key)) - .map_err(|_| format_err!("Autocrypt key cannot be verified")) + .context("autocrypt key cannot be verified") })?; let prefer_encrypt = attributes diff --git a/src/chat.rs b/src/chat.rs index 81ea5adef..bb670c06f 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -620,7 +620,10 @@ impl ChatId { /// Returns `true`, if message was deleted, `false` otherwise. async fn maybe_delete_draft(self, context: &Context) -> Result { 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), } } @@ -640,7 +643,7 @@ impl ChatId { .param .get_blob(Param::File, context, !msg.is_increation()) .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()); } } @@ -1804,9 +1807,7 @@ async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<()> { .param .get_blob(Param::File, context, !msg.is_increation()) .await? - .ok_or_else(|| { - format_err!("Attachment missing for message of type #{}", msg.viewtype) - })?; + .with_context(|| format!("attachment missing for message of type #{}", msg.viewtype))?; if msg.viewtype == Viewtype::Image { if let Err(e) = blob.recode_to_image_size(context).await { @@ -3914,7 +3915,7 @@ mod tests { assert_eq!(chats.len(), 1); // 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)) .await .ok(); @@ -4079,7 +4080,7 @@ mod tests { .unwrap(); let mut result = Vec::new(); 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 } @@ -4502,7 +4503,7 @@ mod tests { let chats = Chatlist::try_load(&t, 0, None, None).await?; 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!(t.get_fresh_msgs().await?.len(), 1); @@ -4550,7 +4551,7 @@ mod tests { let chats = Chatlist::try_load(&t, 0, None, None).await?; 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) .await .unwrap() @@ -4598,7 +4599,7 @@ mod tests { let chats = Chatlist::try_load(&t, 0, None, None).await?; 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_eq!(dc_get_archived_cnt(&t).await?, 0); @@ -4607,13 +4608,13 @@ mod tests { let chats = Chatlist::try_load(&t, 0, None, None).await?; 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_eq!(dc_get_archived_cnt(&t).await?, 1); let chats = Chatlist::try_load(&t, DC_GCL_ARCHIVED_ONLY, None, None).await?; 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()); Ok(()) diff --git a/src/chatlist.rs b/src/chatlist.rs index 32676fbd4..075c83227 100644 --- a/src/chatlist.rs +++ b/src/chatlist.rs @@ -1,6 +1,6 @@ //! # 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::constants::{ @@ -271,21 +271,23 @@ impl Chatlist { /// Get a single chat ID of a chatlist. /// /// To get the message object from the message ID, use dc_get_chat(). - pub fn get_chat_id(&self, index: usize) -> ChatId { - match self.ids.get(index) { - Some((chat_id, _msg_id)) => *chat_id, - None => ChatId::new(0), - } + pub fn get_chat_id(&self, index: usize) -> Result { + let (chat_id, _msg_id) = self + .ids + .get(index) + .context("chatlist index is out of range")?; + Ok(*chat_id) } /// Get a single message ID of a chatlist. /// /// To get the message object from the message ID, use dc_get_msg(). pub fn get_msg_id(&self, index: usize) -> Result> { - match self.ids.get(index) { - Some((_chat_id, msg_id)) => Ok(*msg_id), - None => bail!("Chatlist index out of range"), - } + let (_chat_id, msg_id) = self + .ids + .get(index) + .context("chatlist index is out of range")?; + Ok(*msg_id) } /// 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 // "is typing". // 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) { - Some(ids) => ids, - None => bail!("Chatlist index out of range"), - }; - + let (chat_id, lastmsg_id) = self + .ids + .get(index) + .context("chatlist index is out of range")?; 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 let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap(); assert_eq!(chats.len(), 3); - assert_eq!(chats.get_chat_id(0), chat_id3); - assert_eq!(chats.get_chat_id(1), chat_id2); - assert_eq!(chats.get_chat_id(2), chat_id1); + assert_eq!(chats.get_chat_id(0).unwrap(), chat_id3); + assert_eq!(chats.get_chat_id(1).unwrap(), chat_id2); + assert_eq!(chats.get_chat_id(2).unwrap(), chat_id1); // New drafts are sorted to the top // 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(); - 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 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(); 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 .unwrap() .is_self_talk()); @@ -454,7 +455,7 @@ mod tests { .await .unwrap(); 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 .unwrap() .is_self_talk()); @@ -527,7 +528,7 @@ mod tests { // 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?; 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 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 let chats = Chatlist::try_load(&t, 0, Some("bob@example.org"), None).await?; 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 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 let chats = Chatlist::try_load(&t, 0, Some("Bob Nickname"), None).await?; 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 let test_id = Contact::create(&t, "", "bob@example.org").await?; diff --git a/src/context.rs b/src/context.rs index 1fdb99344..515f432bf 100644 --- a/src/context.rs +++ b/src/context.rs @@ -5,7 +5,7 @@ use std::ffi::OsString; use std::ops::Deref; use std::time::{Instant, SystemTime}; -use anyhow::{bail, ensure, Result}; +use anyhow::{bail, ensure, Context as _, Result}; use async_std::{ channel::{self, Receiver, Sender}, path::{Path, PathBuf}, @@ -155,7 +155,10 @@ impl Context { let ctx = Context { 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) } diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index ba63b6b95..41d5f273a 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -209,7 +209,7 @@ pub(crate) async fn dc_receive_imf_inner( prevent_rename, ) .await - .map_err(|err| err.context("add_parts error"))?; + .context("add_parts error")?; if from_id > DC_CONTACT_ID_LAST_SPECIAL { contact::update_last_seen(context, from_id, sent_timestamp).await?; @@ -1845,11 +1845,11 @@ async fn create_or_lookup_mailinglist( param, ) .await - .map_err(|err| { - err.context(format!( + .with_context(|| { + format!( "Failed to create mailinglist '{}' for grpid={}", &name, &listid - )) + ) })?; 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(); let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap(); 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()); let chat = chat::Chat::load_from_db(&t, chat_id).await.unwrap(); assert!(chat.is_contact_request()); @@ -2539,7 +2539,7 @@ mod tests { dc_receive_imf(&t, GRP_MAIL, "INBOX", false).await.unwrap(); let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap(); 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(); assert_eq!(chat.typ, Chattype::Group); 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 let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap(); 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(); assert!(chat.is_contact_request()); chat_id.accept(&t).await.unwrap(); @@ -3078,7 +3078,7 @@ mod tests { let chats = Chatlist::try_load(&t.ctx, 0, None, None).await?; 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(); let chat = chat::Chat::load_from_db(&t.ctx, chat_id).await?; @@ -3146,7 +3146,7 @@ mod tests { .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(); let chat = Chat::load_from_db(&t.ctx, chat_id).await.unwrap(); assert_eq!(chat.name, "delta-dev"); @@ -3249,7 +3249,7 @@ Hello mailinglist!\r\n" .unwrap(); let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); 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(); assert!(chat.is_contact_request()); diff --git a/src/dc_tools.rs b/src/dc_tools.rs index 79d9b4b0a..a1ecd0e96 100644 --- a/src/dc_tools.rs +++ b/src/dc_tools.rs @@ -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; let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap(); 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) .await .unwrap(); @@ -1202,7 +1202,7 @@ Hop: From: hq5.example.org; By: hq5.example.org; Date: Mon, 27 Dec 2021 11:21:22 .await; let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap(); 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) .await .unwrap(); @@ -1234,7 +1234,7 @@ Hop: From: hq5.example.org; By: hq5.example.org; Date: Mon, 27 Dec 2021 11:21:22 .await; let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap(); 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) .await .unwrap(); @@ -1256,7 +1256,7 @@ Hop: From: hq5.example.org; By: hq5.example.org; Date: Mon, 27 Dec 2021 11:21:22 .await; let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap(); 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) .await .unwrap(); @@ -1273,7 +1273,7 @@ Hop: From: hq5.example.org; By: hq5.example.org; Date: Mon, 27 Dec 2021 11:21:22 .await; let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap(); 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) .await .unwrap(); diff --git a/src/download.rs b/src/download.rs index 1550bfbe5..c0874780f 100644 --- a/src/download.rs +++ b/src/download.rs @@ -196,9 +196,13 @@ impl Imap { // we are connected, and the folder is selected 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) - .await; + .await + { + Ok(res) => res, + Err(_) => return ImapActionResult::Failed, + }; if last_uid.is_none() { ImapActionResult::Failed } else { diff --git a/src/e2ee.rs b/src/e2ee.rs index 2df7ba624..278eefd03 100644 --- a/src/e2ee.rs +++ b/src/e2ee.rs @@ -2,7 +2,7 @@ use std::collections::HashSet; -use anyhow::{bail, format_err, Result}; +use anyhow::{bail, format_err, Context as _, Result}; use mailparse::ParsedMail; use num_traits::FromPrimitive; @@ -121,9 +121,9 @@ impl EncryptHelper { .into_iter() .filter_map(|(state, addr)| state.map(|s| (s, addr))) { - let key = peerstate.take_key(min_verified).ok_or_else(|| { - format_err!("proper enc-key for {} missing, cannot encrypt", addr) - })?; + let key = peerstate + .take_key(min_verified) + .with_context(|| format!("proper enc-key for {} missing, cannot encrypt", addr))?; keyring.add(key); } keyring.add(self.public_key.clone()); @@ -390,12 +390,10 @@ pub async fn ensure_secret_key_exists(context: &Context) -> Result { let self_addr = context .get_config(Config::ConfiguredAddr) .await? - .ok_or_else(|| { - format_err!(concat!( - "Failed to get self address, ", - "cannot ensure secret key if not configured." - )) - })?; + .context(concat!( + "Failed to get self address, ", + "cannot ensure secret key if not configured." + ))?; SignedPublicKey::load_self(context).await?; Ok(self_addr) } diff --git a/src/imap.rs b/src/imap.rs index 509d5bc32..6115e58b0 100644 --- a/src/imap.rs +++ b/src/imap.rs @@ -314,33 +314,24 @@ impl Imap { } }; - let login_res = match connection_res { - Ok(client) => { - let config = &self.config; - let imap_user: &str = config.lp.user.as_ref(); - let imap_pw: &str = config.lp.password.as_ref(); + let client = connection_res?; + let config = &self.config; + let imap_user: &str = config.lp.user.as_ref(); + let imap_pw: &str = config.lp.password.as_ref(); - if oauth2 { - let addr: &str = config.addr.as_ref(); + let login_res = if oauth2 { + let addr: &str = config.addr.as_ref(); - if let Some(token) = - dc_get_oauth2_access_token(context, addr, imap_pw, true).await? - { - let auth = OAuth2 { - user: imap_user.into(), - access_token: token, - }; - client.authenticate("XOAUTH2", auth).await - } else { - bail!("IMAP Could not get OAUTH token"); - } - } else { - client.login(imap_user, imap_pw).await - } - } - Err(err) => { - bail!(err); - } + let token = dc_get_oauth2_access_token(context, addr, imap_pw, true) + .await? + .context("IMAP could not get OAUTH token")?; + let auth = OAuth2 { + user: imap_user.into(), + access_token: token, + }; + client.authenticate("XOAUTH2", auth).await + } else { + client.login(imap_user, imap_pw).await }; self.should_reconnect = false; @@ -397,24 +388,19 @@ impl Imap { return Ok(()); } - match &mut self.session { - Some(ref mut session) => match session.capabilities().await { - Ok(caps) => { - self.config.can_idle = caps.has_str("IDLE"); - self.config.can_move = caps.has_str("MOVE"); - self.config.can_check_quota = caps.has_str("QUOTA"); - self.config.can_condstore = caps.has_str("CONDSTORE"); - self.capabilities_determined = true; - Ok(()) - } - Err(err) => { - bail!("CAPABILITY command error: {}", err); - } - }, - None => { - bail!("Can't determine server capabilities because connection was not established") - } - } + let session = self.session.as_mut().context( + "Can't determine server capabilities because connection was not established", + )?; + let caps = session + .capabilities() + .await + .context("CAPABILITY command error")?; + self.config.can_idle = caps.has_str("IDLE"); + self.config.can_move = caps.has_str("MOVE"); + self.config.can_check_quota = caps.has_str("QUOTA"); + self.config.can_condstore = caps.has_str("CONDSTORE"); + self.capabilities_determined = true; + Ok(()) } /// Prepare for IMAP operation. @@ -505,29 +491,25 @@ impl Imap { self.select_folder(context, Some(&folder)).await?; - let session = if let Some(ref mut session) = &mut self.session { - session - } else { - bail!("IMAP No Connection established"); - }; + let session = self + .session + .as_mut() + .context("IMAP No connection established")?; - match session.uid_fetch("1:*", RFC724MID_UID).await { - Ok(mut list) => { - while let Some(fetch) = list.next().await { - let msg = fetch?; + let mut list = session + .uid_fetch("1:*", RFC724MID_UID) + .await + .with_context(|| format!("can't resync folder {}", folder))?; + while let Some(fetch) = list.next().await { + let msg = fetch?; - // Get Message-ID - let message_id = get_fetch_headers(&msg) - .and_then(|headers| prefetch_get_message_id(&headers)) - .ok(); + // Get Message-ID + let message_id = get_fetch_headers(&msg) + .and_then(|headers| prefetch_get_message_id(&headers)) + .ok(); - if let (Some(uid), Some(rfc724_mid)) = (msg.uid, message_id) { - msg_ids.insert(uid, rfc724_mid); - } - } - } - Err(err) => { - bail!("Can't resync folder {}: {}", folder, err); + if let (Some(uid), Some(rfc724_mid)) = (msg.uid, message_id) { + msg_ids.insert(uid, rfc724_mid); } } @@ -575,9 +557,11 @@ impl Imap { ) -> Result { let newly_selected = self.select_or_create_folder(context, folder).await?; - let mailbox = &mut self.config.selected_mailbox.as_ref(); - let mailbox = - mailbox.with_context(|| format!("No mailbox selected, folder: {}", folder))?; + let mailbox = self + .config + .selected_mailbox + .as_mut() + .with_context(|| format!("No mailbox selected, folder: {}", folder))?; let new_uid_validity = mailbox .uid_validity @@ -806,7 +790,7 @@ impl Imap { false, fetch_existing_msgs, ) - .await; + .await?; let (largest_uid_partially_fetched, received_msgs_2) = self .fetch_many_msgs( @@ -816,7 +800,7 @@ impl Imap { true, fetch_existing_msgs, ) - .await; + .await?; received_msgs.extend(received_msgs_2); // 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. pub async fn get_all_recipients(&mut self, context: &Context) -> Result> { - if self.session.is_none() { - bail!("IMAP No Connection established"); - } - - let session = self.session.as_mut().unwrap(); + let session = self + .session + .as_mut() + .context("IMAP No Connection established")?; let self_addr = context .get_config(Config::ConfiguredAddr) .await? - .ok_or_else(|| format_err!("Not configured"))?; + .context("not configured")?; let search_command = format!("FROM \"{}\"", self_addr); let uids = session @@ -1143,9 +1126,7 @@ impl Imap { let mut list = session .uid_fetch(uid_set, "(UID BODY.PEEK[HEADER.FIELDS (FROM TO CC BCC)])") .await - .map_err(|err| { - format_err!("IMAP Could not fetch (get_all_recipients()): {}", err) - })?; + .context("IMAP Could not fetch")?; while let Some(fetch) = list.next().await { let msg = fetch?; @@ -1177,7 +1158,7 @@ impl Imap { let mut list = session .uid_fetch(set, PREFETCH_FLAGS) .await - .map_err(|err| format_err!("IMAP Could not fetch: {}", err))?; + .context("IMAP could not fetch")?; let mut msgs = BTreeMap::new(); 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) async fn prefetch_existing_msgs(&mut self) -> Result> { let exists: i64 = { - let mailbox = self.config.selected_mailbox.as_ref(); - let mailbox = mailbox.context("fetch_existing_msgs_prefetch(): no mailbox selected")?; + let mailbox = self + .config + .selected_mailbox + .as_ref() + .context("no mailbox")?; mailbox.exists.into() }; - let session = self.session.as_mut(); - let session = - session.context("fetch_existing_msgs_prefetch(): IMAP No Connection established")?; + let session = self.session.as_mut().context("no IMAP session")?; // Fetch last DC_FETCH_EXISTING_MSGS_COUNT (100) messages. // Sequence numbers are sequential. If there are 1000 messages in the inbox, @@ -1219,7 +1201,7 @@ impl Imap { let mut list = session .fetch(&set, PREFETCH_FLAGS) .await - .map_err(|err| format_err!("IMAP Could not fetch: {}", err))?; + .context("IMAP Could not fetch")?; let mut msgs = BTreeMap::new(); while let Some(fetch) = list.next().await { @@ -1242,19 +1224,13 @@ impl Imap { server_uids: Vec, fetch_partially: bool, fetching_existing_messages: bool, - ) -> (Option, Vec) { + ) -> Result<(Option, Vec)> { let mut received_msgs = Vec::new(); if server_uids.is_empty() { - return (None, Vec::new()); + return Ok((None, Vec::new())); } - let session = match self.session.as_mut() { - Some(session) => session, - None => { - warn!(context, "Not connected"); - return (None, Vec::new()); - } - }; + let session = self.session.as_mut().context("no IMAP session")?; let sets = build_sequence_sets(server_uids.clone()); let mut count = 0; @@ -1277,14 +1253,12 @@ impl Imap { // TODO: maybe differentiate between IO and input/parsing problems // so we don't reconnect if we have a (rare) input/output parsing problem? self.should_reconnect = true; - warn!( - context, + bail!( "Error on fetching messages #{} from folder \"{}\"; error={}.", &set, folder, 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 { - // return true if we successfully set the flag or we otherwise - // think add_flag should not be retried: Disconnection during setting - // the flag, or other imap-errors, returns true as well. - // - // returning false means that the operation can be retried. - if server_uid == 0 { - return true; // might be moved but we don't want to have a stuck job - } + /// Returns success if we successfully set the flag or we otherwise + /// think add_flag should not be retried: Disconnection during setting + /// the flag, or other imap-errors, returns true as well. + /// + /// Returning error means that the operation can be retried. + async fn add_flag_finalized(&mut self, server_uid: u32, flag: &str) -> Result<()> { 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( - &mut self, - context: &Context, - uid_set: &str, - flag: &str, - ) -> bool { + async fn add_flag_finalized_with_set(&mut self, uid_set: &str, flag: &str) -> Result<()> { 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); - match session.uid_store(uid_set, &query).await { - Ok(mut responses) => { - while let Some(_response) = responses.next().await { - // Read all the responses - } - } - Err(err) => { - warn!( - context, - "IMAP failed to store: ({}, {}) {:?}", uid_set, query, err - ); - } - } - true // we tried once, that's probably enough for setting flag - } else { - unreachable!(); + + let session = self.session.as_mut().context("No session").unwrap(); + let query = format!("+FLAGS ({})", flag); + let mut responses = session + .uid_store(uid_set, &query) + .await + .with_context(|| format!("IMAP failed to store: ({}, {})", uid_set, query))?; + while let Some(_response) = responses.next().await { + // Read all the responses } + Ok(()) } 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, context: &Context, folder: &str, @@ -1464,14 +1422,14 @@ impl Imap { // we are connected, and the folder is selected info!(context, "Marking message {}/{} as seen...", folder, uid,); - if self.add_flag_finalized(context, uid, "\\Seen").await { - ImapActionResult::Success - } else { + if let Err(err) = self.add_flag_finalized(uid, "\\Seen").await { warn!( context, - "Cannot mark message {} in folder {} as seen, ignoring.", uid, folder + "Cannot mark message {} in folder {} as seen, ignoring: {}.", uid, folder, err ); ImapActionResult::Failed + } else { + ImapActionResult::Success } } @@ -1492,10 +1450,10 @@ impl Imap { let display_imap_id = format!("{}/{}", folder, uid); // 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!( context, - "Cannot mark message {} as \"Deleted\".", display_imap_id + "Cannot mark message {} as \"Deleted\": {}.", display_imap_id, err ); ImapActionResult::RetryLater } else { @@ -1522,18 +1480,15 @@ impl Imap { } pub async fn configure_folders(&mut self, context: &Context, create_mvbox: bool) -> Result<()> { - let session = match self.session { - Some(ref mut session) => session, - None => bail!("no IMAP connection established"), - }; - - let mut folders = match session.list(Some(""), Some("*")).await { - Ok(f) => f, - Err(err) => { - bail!("list_folders failed: {}", err); - } - }; + let session = self + .session + .as_mut() + .context("no IMAP connection established")?; + let mut folders = session + .list(Some(""), Some("*")) + .await + .context("list_folders failed")?; let mut delimiter = ".".to_string(); let mut delimiter_is_default = true; let mut mvbox_folder = None; @@ -1638,11 +1593,8 @@ impl Imap { /// Drains all responses from `session.unsolicited_responses` in the process. /// If this returns `true`, this means that new emails arrived and you should /// fetch again, even if you just fetched. - fn server_sent_unsolicited_exists(&self, context: &Context) -> bool { - let session = match &self.session { - Some(s) => s, - None => return false, - }; + fn server_sent_unsolicited_exists(&self, context: &Context) -> Result { + let session = self.session.as_ref().context("no session")?; let mut unsolicited_exists = false; while let Ok(response) = session.unsolicited_responses.try_recv() { match response { @@ -1656,7 +1608,7 @@ impl Imap { _ => info!(context, "ignoring unsolicited response {:?}", response), } } - unsolicited_exists + Ok(unsolicited_exists) } pub fn can_check_quota(&self) -> bool { diff --git a/src/imap/idle.rs b/src/imap/idle.rs index de5b31fd1..ffff87070 100644 --- a/src/imap/idle.rs +++ b/src/imap/idle.rs @@ -1,6 +1,6 @@ use super::Imap; -use anyhow::{bail, format_err, Result}; +use anyhow::{bail, Context as _, Result}; use async_imap::extensions::idle::IdleResponse; use async_std::prelude::*; use std::time::{Duration, SystemTime}; @@ -31,7 +31,7 @@ impl Imap { let timeout = Duration::from_secs(23 * 60); let mut info = Default::default(); - if self.server_sent_unsolicited_exists(context) { + if self.server_sent_unsolicited_exists(context)? { return Ok(info); } @@ -90,8 +90,8 @@ impl Imap { let session = handle .done() .timeout(Duration::from_secs(15)) - .await - .map_err(|err| format_err!("IMAP IDLE protocol timed out: {}", err))??; + .await? + .context("IMAP IDLE protocol timed out")?; self.session = Some(Session { inner: session }); } else { warn!(context, "Attempted to idle without a session"); diff --git a/src/imap/scan_folders.rs b/src/imap/scan_folders.rs index d011aa879..2fcef3505 100644 --- a/src/imap/scan_folders.rs +++ b/src/imap/scan_folders.rs @@ -71,7 +71,7 @@ impl Imap { // Don't scan folders that are watched anyway if !watched_folders.contains(&folder.name().to_string()) && !is_drafts { // Drain leftover unsolicited EXISTS messages - self.server_sent_unsolicited_exists(context); + self.server_sent_unsolicited_exists(context)?; loop { 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"); // 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; } } diff --git a/src/job.rs b/src/job.rs index 516da5316..98952dc75 100644 --- a/src/job.rs +++ b/src/job.rs @@ -372,13 +372,13 @@ impl Job { let filename = job_try!(job_try!(self .param .get_path(Param::File, context) - .map_err(|_| format_err!("Can't get filename"))) - .ok_or_else(|| format_err!("Can't get filename"))); + .context("can't get filename")) + .context("Can't get filename")); let body = job_try!(dc_read_file(context, &filename).await); - let recipients = job_try!(self.param.get(Param::Recipients).ok_or_else(|| { - warn!(context, "Missing recipients for job {}", self.job_id); - format_err!("Missing recipients") - })); + let recipients = job_try!(self + .param + .get(Param::Recipients) + .context("missing recipients")); let recipients_list = recipients .split('\x1e') diff --git a/src/key.rs b/src/key.rs index 9668fcc62..5d1276b63 100644 --- a/src/key.rs +++ b/src/key.rs @@ -4,7 +4,7 @@ use std::collections::BTreeMap; use std::fmt; use std::io::Cursor; -use anyhow::{format_err, Result}; +use anyhow::{format_err, Context as _, Result}; use async_trait::async_trait; use num_traits::FromPrimitive; use pgp::composed::Deserializable; @@ -50,8 +50,7 @@ pub trait DcKey: Serialize + Deserializable + KeyTrait + Clone { /// the ASCII-armored representation. fn from_asc(data: &str) -> Result<(Self::KeyType, BTreeMap)> { let bytes = data.as_bytes(); - Self::KeyType::from_armor_single(Cursor::new(bytes)) - .map_err(|err| format_err!("rPGP error: {}", err)) + Self::KeyType::from_armor_single(Cursor::new(bytes)).context("rPGP error") } /// Load the users' default key from the database. @@ -202,7 +201,7 @@ async fn generate_keypair(context: &Context) -> Result { let addr = context .get_config(Config::ConfiguredAddr) .await? - .ok_or_else(|| format_err!("No address configured"))?; + .context("no address configured")?; let addr = EmailAddress::new(&addr)?; let _guard = context.generating_key_mutex.lock().await; @@ -289,13 +288,13 @@ pub async fn store_self_keypair( paramsv![public_key, secret_key], ) .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 { context .sql .execute("UPDATE keypairs SET is_default=0;", paramsv![]) .await - .map_err(|err| err.context("failed to clear default"))?; + .context("failed to clear default")?; } let is_default = match default { KeyPairUse::Default => true as i32, @@ -313,7 +312,7 @@ pub async fn store_self_keypair( paramsv![addr, is_default, public_key, secret_key, t], ) .await - .map_err(|err| err.context("failed to insert keypair"))?; + .context("failed to insert keypair")?; Ok(()) } diff --git a/src/message.rs b/src/message.rs index 6815b0756..47aa30b3e 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1534,9 +1534,7 @@ async fn ndn_maybe_add_info_msg( let contact_id = Contact::lookup_id_by_addr(context, failed_recipient, Origin::Unknown) .await? - .ok_or_else(|| { - format_err!("ndn_maybe_add_info_msg: Contact ID not found") - })?; + .context("contact ID not found")?; let contact = Contact::load_from_db(context, contact_id).await?; // Tell the user which of the recipients failed if we know that (because in // a group, this might otherwise be unclear) @@ -1988,9 +1986,9 @@ mod tests { let msg2 = alice.get_last_msg().await; let chats = Chatlist::try_load(&alice, 0, None, None).await?; assert_eq!(chats.len(), 1); - 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), msg2.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)?, msg2.chat_id); assert_eq!(alice_chat.id.get_fresh_msg_cnt(&alice).await?, 2); assert_eq!(alice.get_fresh_msgs().await?.len(), 2); diff --git a/src/mimefactory.rs b/src/mimefactory.rs index 830701b2b..7a6d7af73 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -2,7 +2,7 @@ use std::convert::TryInto; -use anyhow::{bail, ensure, format_err, Context as _, Result}; +use anyhow::{bail, ensure, Context as _, Result}; use chrono::TimeZone; use lettre_email::{mime, Address, Header, MimeMultipartType, PartBuilder}; @@ -281,7 +281,7 @@ impl<'a> MimeFactory<'a> { let self_addr = context .get_config(Config::ConfiguredAddr) .await? - .ok_or_else(|| format_err!("Not configured"))?; + .context("not configured")?; let mut res = Vec::new(); for (_, addr) in self @@ -1283,7 +1283,7 @@ async fn build_body_file( .param .get_blob(Param::File, context, true) .await? - .ok_or_else(|| format_err!("msg has no filename"))?; + .context("msg has no filename")?; let suffix = blob.suffix().unwrap_or("dat"); // 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 chat_id = chats.get_chat_id(0); + let chat_id = chats.get_chat_id(0).unwrap(); chat_id.accept(context).await.unwrap(); let mut new_msg = Message::new(Viewtype::Text); @@ -2058,11 +2058,11 @@ mod tests { let to = parsed .headers .get_first_header("To") - .ok_or_else(|| format_err!("No To: header parsed"))?; + .context("no To: header parsed")?; let to = addrparse_header(to)?; let mailbox = to .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"); Ok(()) diff --git a/src/pgp.rs b/src/pgp.rs index 7a6f86c27..e9236fcde 100644 --- a/src/pgp.rs +++ b/src/pgp.rs @@ -4,7 +4,7 @@ use std::collections::{BTreeMap, HashSet}; use std::io; 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::composed::{ Deserializable, KeyType as PgpKeyType, Message, SecretKeyParamsBuilder, SignedPublicKey, @@ -346,7 +346,7 @@ pub async fn pk_validate( // OpenPGP signature calculation. let content = content .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 { if standalone_signature.verify(pkey, content).is_ok() { @@ -520,7 +520,6 @@ mod tests { &sig_check_keyring, ) .await - .map_err(|err| println!("{:?}", err)) .unwrap(); assert_eq!(plain, CLEARTEXT); assert_eq!(valid_signatures.len(), 1); @@ -536,7 +535,6 @@ mod tests { &sig_check_keyring, ) .await - .map_err(|err| println!("{:?}", err)) .unwrap(); assert_eq!(plain, CLEARTEXT); assert_eq!(valid_signatures.len(), 1); diff --git a/src/qr.rs b/src/qr.rs index f36eef738..9e91a2ecd 100644 --- a/src/qr.rs +++ b/src/qr.rs @@ -304,14 +304,14 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result { fn decode_account(qr: &str) -> Result { let payload = qr .get(DCACCOUNT_SCHEME.len()..) - .ok_or_else(|| format_err!("Invalid DCACCOUNT payload"))?; + .context("invalid DCACCOUNT payload")?; let url = url::Url::parse(payload).with_context(|| format!("Invalid account URL: {:?}", payload))?; if url.scheme() == "http" || url.scheme() == "https" { Ok(Qr::Account { domain: url .host_str() - .ok_or_else(|| format_err!("Can't extract WebRTC instance domain"))? + .context("can't extract WebRTC instance domain")? .to_string(), }) } else { @@ -323,7 +323,7 @@ fn decode_account(qr: &str) -> Result { fn decode_webrtc_instance(_context: &Context, qr: &str) -> Result { let payload = qr .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 url = @@ -333,7 +333,7 @@ fn decode_webrtc_instance(_context: &Context, qr: &str) -> Result { Ok(Qr::WebrtcInstance { domain: url .host_str() - .ok_or_else(|| format_err!("Can't extract WebRTC instance domain"))? + .context("can't extract WebRTC instance domain")? .to_string(), instance_pattern: payload.to_string(), }) diff --git a/src/quota.rs b/src/quota.rs index de5560e3c..133db6595 100644 --- a/src/quota.rs +++ b/src/quota.rs @@ -1,6 +1,6 @@ //! # Support for IMAP QUOTA extension. -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, Context as _, Result}; use async_imap::types::{Quota, QuotaResource}; use std::collections::BTreeMap; @@ -64,7 +64,7 @@ async fn get_unique_quota_roots_and_usage( .iter() .find(|q| &q.root_name == quota_root_name) .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, // messages could be recieved and so the usage could have been changed *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. diff --git a/src/sql.rs b/src/sql.rs index a83945cac..7e7510cae 100644 --- a/src/sql.rs +++ b/src/sql.rs @@ -7,7 +7,7 @@ use std::collections::HashSet; use std::convert::TryFrom; use std::time::Duration; -use anyhow::{bail, format_err, Context as _, Result}; +use anyhow::{bail, Context as _, Result}; use async_std::prelude::*; use rusqlite::OpenFlags; @@ -145,7 +145,9 @@ impl Sql { // rely themselves on the low-level structure. 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 // the structure is complete now and all objects are usable @@ -261,9 +263,7 @@ impl Sql { &self, ) -> Result> { let lock = self.pool.read().await; - let pool = lock - .as_ref() - .ok_or_else(|| format_err!("No SQL connection"))?; + let pool = lock.as_ref().context("no SQL connection")?; let conn = pool.get()?; Ok(conn) diff --git a/src/sql/migrations.rs b/src/sql/migrations.rs index 864904c99..23d86d0c7 100644 --- a/src/sql/migrations.rs +++ b/src/sql/migrations.rs @@ -1,6 +1,6 @@ //! Migrations module. -use anyhow::Result; +use anyhow::{Context as _, Result}; use crate::config::Config; 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 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",); sql.transaction(move |transaction| { transaction.execute_batch(TABLES)?; @@ -572,7 +576,8 @@ impl Sql { Ok(()) }) - .await?; + .await + .with_context(|| format!("execute_migration failed for version {}", version))?; Ok(()) } diff --git a/src/stock_str.rs b/src/stock_str.rs index 0ab3239f5..aeb9f8086 100644 --- a/src/stock_str.rs +++ b/src/stock_str.rs @@ -1280,11 +1280,13 @@ mod tests { let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap(); 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() { - (chats.get_chat_id(0), chats.get_chat_id(1)) + (chats.get_chat_id(0).unwrap(), chats.get_chat_id(1).unwrap()) } 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