From 9614a2350644cf6a30272d4d1b8bf82c4305339f Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 4 Mar 2020 14:48:15 +0100 Subject: [PATCH 001/118] first pass at async job --- Cargo.toml | 2 + examples/simple.rs | 28 +-- src/chat.rs | 140 ++++++++------- src/config.rs | 18 +- src/configure/mod.rs | 55 +++--- src/contact.rs | 6 +- src/context.rs | 44 +++-- src/dc_receive_imf.rs | 28 +-- src/e2ee.rs | 12 +- src/imap/idle.rs | 371 +++++++++++++++++++------------------- src/imap/mod.rs | 407 +++++++++++++++++++++--------------------- src/imex.rs | 28 +-- src/job.rs | 279 +++++++++++++++-------------- src/job_thread.rs | 20 +-- src/location.rs | 38 ++-- src/message.rs | 36 ++-- src/mimeparser.rs | 6 +- src/qr.rs | 10 +- src/securejoin.rs | 29 +-- src/stock.rs | 8 +- src/test_utils.rs | 3 +- 21 files changed, 838 insertions(+), 730 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 55ac0054a..eb42deba5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,12 +60,14 @@ image = { version = "0.22.4", default-features=false, features = ["gif_codec", " pretty_env_logger = "0.3.1" rustyline = { version = "4.1.0", optional = true } +futures = "0.3.4" [dev-dependencies] tempfile = "3.0" pretty_assertions = "0.6.1" pretty_env_logger = "0.3.0" proptest = "0.9.4" +async-std = { version = "1.4", features = ["unstable", "attributes"] } [workspace] members = [ diff --git a/examples/simple.rs b/examples/simple.rs index 9ab89ed6d..afcd84906 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -31,7 +31,8 @@ fn cb(_ctx: &Context, event: Event) { } } -fn main() { +#[async_std::main] +async fn main() { let dir = tempdir().unwrap(); let dbfile = dir.path().join("db.sqlite"); println!("creating database {:?}", dbfile); @@ -47,12 +48,12 @@ fn main() { let r1 = running.clone(); let t1 = thread::spawn(move || { while *r1.read().unwrap() { - perform_inbox_jobs(&ctx1); + async_std::task::block_on(perform_inbox_jobs(&ctx1)); if *r1.read().unwrap() { - perform_inbox_fetch(&ctx1); + async_std::task::block_on(perform_inbox_fetch(&ctx1)); if *r1.read().unwrap() { - perform_inbox_idle(&ctx1); + async_std::task::block_on(perform_inbox_idle(&ctx1)); } } } @@ -62,9 +63,9 @@ fn main() { let r1 = running.clone(); let t2 = thread::spawn(move || { while *r1.read().unwrap() { - perform_smtp_jobs(&ctx1); + async_std::task::block_on(perform_smtp_jobs(&ctx1)); if *r1.read().unwrap() { - perform_smtp_idle(&ctx1); + async_std::task::block_on(perform_smtp_idle(&ctx1)); } } }); @@ -74,16 +75,21 @@ fn main() { assert_eq!(args.len(), 2, "missing password"); let pw = args[1].clone(); ctx.set_config(config::Config::Addr, Some("d@testrun.org")) + .await .unwrap(); - ctx.set_config(config::Config::MailPw, Some(&pw)).unwrap(); - ctx.configure(); + ctx.set_config(config::Config::MailPw, Some(&pw)) + .await + .unwrap(); + ctx.configure().await; thread::sleep(duration); println!("sending a message"); let contact_id = Contact::create(&ctx, "dignifiedquire", "dignifiedquire@gmail.com").unwrap(); let chat_id = chat::create_by_contact_id(&ctx, contact_id).unwrap(); - chat::send_text_msg(&ctx, chat_id, "Hi, here is my first message!".into()).unwrap(); + chat::send_text_msg(&ctx, chat_id, "Hi, here is my first message!".into()) + .await + .unwrap(); println!("fetching chats.."); let chats = Chatlist::try_load(&ctx, 0, None, None).unwrap(); @@ -100,8 +106,8 @@ fn main() { println!("stopping threads"); *running.write().unwrap() = false; - deltachat::job::interrupt_inbox_idle(&ctx); - deltachat::job::interrupt_smtp_idle(&ctx); + deltachat::job::interrupt_inbox_idle(&ctx).await; + deltachat::job::interrupt_smtp_idle(&ctx).await; println!("joining"); t1.join().unwrap(); diff --git a/src/chat.rs b/src/chat.rs index f0f3d0e6d..a111cdb0a 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -17,7 +17,7 @@ use crate::context::Context; use crate::dc_tools::*; use crate::error::Error; use crate::events::Event; -use crate::job::*; +use crate::job::{self, Action}; use crate::message::{self, InvalidMsgId, Message, MessageState, MsgId}; use crate::mimeparser::SystemMessage; use crate::param::*; @@ -185,7 +185,7 @@ impl ChatId { } /// Deletes a chat. - pub fn delete(self, context: &Context) -> Result<(), Error> { + pub async fn delete(self, context: &Context) -> Result<(), Error> { ensure!( !self.is_special(), "bad chat_id, can not be a special chat: {}", @@ -227,8 +227,8 @@ impl ChatId { chat_id: ChatId::new(0), }); - job_kill_action(context, Action::Housekeeping); - job_add(context, Action::Housekeeping, 0, Params::new(), 10); + job::kill_action(context, Action::Housekeeping).await; + job::add(context, Action::Housekeeping, 0, Params::new(), 10).await; Ok(()) } @@ -1378,7 +1378,39 @@ pub fn is_contact_in_chat(context: &Context, chat_id: ChatId, contact_id: u32) - // TODO: Do not allow ChatId to be 0, if prepare_msg had been called // the caller can get it from msg.chat_id. Forwards would need to // be fixed for this somehow too. -pub fn send_msg(context: &Context, chat_id: ChatId, msg: &mut Message) -> Result { +pub async fn send_msg( + context: &Context, + chat_id: ChatId, + msg: &mut Message, +) -> Result { + if chat_id.is_unset() { + let forwards = msg.param.get(Param::PrepForwards); + if let Some(forwards) = forwards { + for forward in forwards.split(' ') { + if let Ok(msg_id) = forward + .parse::() + .map_err(|_| InvalidMsgId) + .map(MsgId::new) + { + if let Ok(mut msg) = Message::load_from_db(context, msg_id) { + send_msg_inner(context, chat_id, &mut msg).await?; + }; + } + } + msg.param.remove(Param::PrepForwards); + msg.save_param_to_disk(context); + } + return send_msg_inner(context, chat_id, msg).await; + } + + send_msg_inner(context, chat_id, msg).await +} + +async fn send_msg_inner( + context: &Context, + chat_id: ChatId, + msg: &mut Message, +) -> Result { // dc_prepare_msg() leaves the message state to OutPreparing, we // only have to change the state to OutPending in this case. // Otherwise we still have to prepare the message, which will set @@ -1394,8 +1426,7 @@ pub fn send_msg(context: &Context, chat_id: ChatId, msg: &mut Message) -> Result ); message::update_msg_state(context, msg.id, MessageState::OutPending); } - - job_send_msg(context, msg.id)?; + job::send_msg(context, msg.id).await?; context.call_cb(Event::MsgsChanged { chat_id: msg.chat_id, @@ -1406,29 +1437,10 @@ pub fn send_msg(context: &Context, chat_id: ChatId, msg: &mut Message) -> Result context.call_cb(Event::LocationChanged(Some(DC_CONTACT_ID_SELF))); } - if chat_id.is_unset() { - let forwards = msg.param.get(Param::PrepForwards); - if let Some(forwards) = forwards { - for forward in forwards.split(' ') { - if let Ok(msg_id) = forward - .parse::() - .map_err(|_| InvalidMsgId) - .map(MsgId::new) - { - if let Ok(mut msg) = Message::load_from_db(context, msg_id) { - send_msg(context, chat_id, &mut msg)?; - }; - } - } - msg.param.remove(Param::PrepForwards); - msg.save_param_to_disk(context); - } - } - Ok(msg.id) } -pub fn send_text_msg( +pub async fn send_text_msg( context: &Context, chat_id: ChatId, text_to_send: String, @@ -1441,7 +1453,7 @@ pub fn send_text_msg( let mut msg = Message::new(Viewtype::Text); msg.text = Some(text_to_send); - send_msg(context, chat_id, &mut msg) + send_msg(context, chat_id, &mut msg).await } pub fn get_chat_msgs( @@ -1783,8 +1795,8 @@ pub(crate) fn remove_from_chat_contacts_table( } /// Adds a contact to the chat. -pub fn add_contact_to_chat(context: &Context, chat_id: ChatId, contact_id: u32) -> bool { - match add_contact_to_chat_ex(context, chat_id, contact_id, false) { +pub async fn add_contact_to_chat(context: &Context, chat_id: ChatId, contact_id: u32) -> bool { + match add_contact_to_chat_ex(context, chat_id, contact_id, false).await { Ok(res) => res, Err(err) => { error!(context, "failed to add contact: {}", err); @@ -1793,7 +1805,7 @@ pub fn add_contact_to_chat(context: &Context, chat_id: ChatId, contact_id: u32) } } -pub(crate) fn add_contact_to_chat_ex( +pub(crate) async fn add_contact_to_chat_ex( context: &Context, chat_id: ChatId, contact_id: u32, @@ -1873,7 +1885,7 @@ pub(crate) fn add_contact_to_chat_ex( msg.param.set_cmd(SystemMessage::MemberAddedToGroup); msg.param.set(Param::Arg, contact.get_addr()); msg.param.set_int(Param::Arg2, from_handshake.into()); - msg.id = send_msg(context, chat_id, &mut msg)?; + msg.id = send_msg(context, chat_id, &mut msg).await?; context.call_cb(Event::MsgsChanged { chat_id, msg_id: msg.id, @@ -2034,7 +2046,7 @@ pub fn set_muted(context: &Context, chat_id: ChatId, duration: MuteDuration) -> Ok(()) } -pub fn remove_contact_from_chat( +pub async fn remove_contact_from_chat( context: &Context, chat_id: ChatId, contact_id: u32, @@ -2086,7 +2098,7 @@ pub fn remove_contact_from_chat( } msg.param.set_cmd(SystemMessage::MemberRemovedFromGroup); msg.param.set(Param::Arg, contact.get_addr()); - msg.id = send_msg(context, chat_id, &mut msg)?; + msg.id = send_msg(context, chat_id, &mut msg).await?; context.call_cb(Event::MsgsChanged { chat_id, msg_id: msg.id, @@ -2134,7 +2146,7 @@ pub(crate) fn is_group_explicitly_left( .map_err(Into::into) } -pub fn set_chat_name( +pub async fn set_chat_name( context: &Context, chat_id: ChatId, new_name: impl AsRef, @@ -2178,7 +2190,7 @@ pub fn set_chat_name( if !chat.name.is_empty() { msg.param.set(Param::Arg, &chat.name); } - msg.id = send_msg(context, chat_id, &mut msg)?; + msg.id = send_msg(context, chat_id, &mut msg).await?; context.call_cb(Event::MsgsChanged { chat_id, msg_id: msg.id, @@ -2202,7 +2214,7 @@ pub fn set_chat_name( /// The profile image can only be set when you are a member of the /// chat. To remove the profile image pass an empty string for the /// `new_image` parameter. -pub fn set_chat_profile_image( +pub async fn set_chat_profile_image( context: &Context, chat_id: ChatId, new_image: impl AsRef, // XXX use PathBuf @@ -2254,7 +2266,7 @@ pub fn set_chat_profile_image( } chat.update_param(context)?; if chat.is_promoted() { - msg.id = send_msg(context, chat_id, &mut msg)?; + msg.id = send_msg(context, chat_id, &mut msg).await?; emit_event!( context, Event::MsgsChanged { @@ -2267,7 +2279,11 @@ pub fn set_chat_profile_image( Ok(()) } -pub fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: ChatId) -> Result<(), Error> { +pub async fn forward_msgs( + context: &Context, + msg_ids: &[MsgId], + chat_id: ChatId, +) -> Result<(), Error> { ensure!(!msg_ids.is_empty(), "empty msgs_ids: nothing to forward"); ensure!(!chat_id.is_special(), "can not forward to special chat"); @@ -2331,7 +2347,7 @@ pub fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: ChatId) -> Re let fresh10 = curr_timestamp; curr_timestamp += 1; new_msg_id = chat.prepare_msg_raw(context, &mut msg, fresh10)?; - job_send_msg(context, new_msg_id)?; + job::send_msg(context, new_msg_id).await?; } created_chats.push(chat_id); created_msgs.push(new_msg_id); @@ -2591,12 +2607,14 @@ mod tests { assert_eq!(msg_text, draft_text); } - #[test] - fn test_add_contact_to_chat_ex_add_self() { + #[async_std::test] + async fn test_add_contact_to_chat_ex_add_self() { // Adding self to a contact should succeed, even though it's pointless. let t = test_context(Some(Box::new(logging_cb))); let chat_id = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo").unwrap(); - let added = add_contact_to_chat_ex(&t.ctx, chat_id, DC_CONTACT_ID_SELF, false).unwrap(); + let added = add_contact_to_chat_ex(&t.ctx, chat_id, DC_CONTACT_ID_SELF, false) + .await + .unwrap(); assert_eq!(added, false); } @@ -2664,8 +2682,8 @@ mod tests { assert_eq!(msg2.chat_id.get_msg_cnt(&t.ctx), 2); } - #[test] - fn test_add_device_msg_labelled() { + #[async_std::test] + async fn test_add_device_msg_labelled() { let t = test_context(Some(Box::new(logging_cb))); // add two device-messages with the same label (second attempt is not added) @@ -2707,7 +2725,7 @@ mod tests { assert!(chat.get_profile_image(&t.ctx).is_some()); // delete device message, make sure it is not added again - message::delete_msgs(&t.ctx, &[*msg1_id.as_ref().unwrap()]); + message::delete_msgs(&t.ctx, &[*msg1_id.as_ref().unwrap()]).await; let msg1 = message::Message::load_from_db(&t.ctx, *msg1_id.as_ref().unwrap()); assert!(msg1.is_err() || msg1.unwrap().chat_id.is_trash()); let msg3_id = add_device_msg(&t.ctx, Some("any-label"), Some(&mut msg2)); @@ -2751,8 +2769,8 @@ mod tests { assert!(was_device_msg_ever_added(&t.ctx, "").is_err()); } - #[test] - fn test_delete_device_chat() { + #[async_std::test] + async fn test_delete_device_chat() { let t = test_context(Some(Box::new(logging_cb))); let mut msg = Message::new(Viewtype::Text); @@ -2762,13 +2780,13 @@ 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.ctx).ok(); + chats.get_chat_id(0).delete(&t.ctx).await.ok(); add_device_msg(&t.ctx, Some("some-label"), Some(&mut msg)).ok(); assert_eq!(chatlist_len(&t.ctx, 0), 0) } - #[test] - fn test_device_chat_cannot_sent() { + #[async_std::test] + async fn test_device_chat_cannot_sent() { let t = test_context(Some(Box::new(logging_cb))); t.ctx.update_device_chats().unwrap(); let (device_chat_id, _) = @@ -2776,11 +2794,13 @@ mod tests { let mut msg = Message::new(Viewtype::Text); msg.text = Some("message text".to_string()); - assert!(send_msg(&t.ctx, device_chat_id, &mut msg).is_err()); + assert!(send_msg(&t.ctx, device_chat_id, &mut msg).await.is_err()); assert!(prepare_msg(&t.ctx, device_chat_id, &mut msg).is_err()); let msg_id = add_device_msg(&t.ctx, None, Some(&mut msg)).unwrap(); - assert!(forward_msgs(&t.ctx, &[msg_id], device_chat_id).is_err()); + assert!(forward_msgs(&t.ctx, &[msg_id], device_chat_id) + .await + .is_err()); } #[test] @@ -2957,8 +2977,8 @@ mod tests { assert_eq!(chatlist, vec![chat_id3, chat_id2, chat_id1]); } - #[test] - fn test_set_chat_name() { + #[async_std::test] + async fn test_set_chat_name() { let t = dummy_context(); let chat_id = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo").unwrap(); assert_eq!( @@ -2966,7 +2986,7 @@ mod tests { "foo" ); - set_chat_name(&t.ctx, chat_id, "bar").unwrap(); + set_chat_name(&t.ctx, chat_id, "bar").await.unwrap(); assert_eq!( Chat::load_from_db(&t.ctx, chat_id).unwrap().get_name(), "bar" @@ -2990,17 +3010,17 @@ mod tests { assert_eq!(chat2.name, chat.name); } - #[test] - fn test_shall_attach_selfavatar() { + #[async_std::test] + async fn test_shall_attach_selfavatar() { let t = dummy_context(); let chat_id = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo").unwrap(); assert!(!shall_attach_selfavatar(&t.ctx, chat_id).unwrap()); let (contact_id, _) = Contact::add_or_lookup(&t.ctx, "", "foo@bar.org", Origin::IncomingUnknownTo).unwrap(); - add_contact_to_chat(&t.ctx, chat_id, contact_id); + add_contact_to_chat(&t.ctx, chat_id, contact_id).await; assert!(!shall_attach_selfavatar(&t.ctx, chat_id).unwrap()); - t.ctx.set_config(Config::Selfavatar, None).unwrap(); // setting to None also forces re-sending + t.ctx.set_config(Config::Selfavatar, None).await.unwrap(); // setting to None also forces re-sending assert!(shall_attach_selfavatar(&t.ctx, chat_id).unwrap()); assert!(chat_id.set_selfavatar_timestamp(&t.ctx, time()).is_ok()); diff --git a/src/config.rs b/src/config.rs index a0ef749ff..9d437ef6b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -130,7 +130,7 @@ impl Context { /// Set the given config key. /// If `None` is passed as a value the value is cleared and set to the default if there is one. - pub fn set_config(&self, key: Config, value: Option<&str>) -> crate::sql::Result<()> { + pub async fn set_config(&self, key: Config, value: Option<&str>) -> crate::sql::Result<()> { match key { Config::Selfavatar => { self.sql @@ -148,17 +148,17 @@ impl Context { } Config::InboxWatch => { let ret = self.sql.set_raw_config(self, key, value); - interrupt_inbox_idle(self); + interrupt_inbox_idle(self).await; ret } Config::SentboxWatch => { let ret = self.sql.set_raw_config(self, key, value); - interrupt_sentbox_idle(self); + interrupt_sentbox_idle(self).await; ret } Config::MvboxWatch => { let ret = self.sql.set_raw_config(self, key, value); - interrupt_mvbox_idle(self); + interrupt_mvbox_idle(self).await; ret } Config::Selfstatus => { @@ -217,8 +217,8 @@ mod tests { assert_eq!(Config::ImapFolder.get_str("default"), Some("INBOX")); } - #[test] - fn test_selfavatar_outside_blobdir() { + #[async_std::test] + async fn test_selfavatar_outside_blobdir() { let t = dummy_context(); let avatar_src = t.dir.path().join("avatar.jpg"); let avatar_bytes = include_bytes!("../test-data/image/avatar1000x1000.jpg"); @@ -230,6 +230,7 @@ mod tests { assert!(!avatar_blob.exists()); t.ctx .set_config(Config::Selfavatar, Some(&avatar_src.to_str().unwrap())) + .await .unwrap(); assert!(avatar_blob.exists()); assert!(std::fs::metadata(&avatar_blob).unwrap().len() < avatar_bytes.len() as u64); @@ -245,8 +246,8 @@ mod tests { assert_eq!(img.height(), AVATAR_SIZE); } - #[test] - fn test_selfavatar_in_blobdir() { + #[async_std::test] + async fn test_selfavatar_in_blobdir() { let t = dummy_context(); let avatar_src = t.ctx.get_blobdir().join("avatar.png"); let avatar_bytes = include_bytes!("../test-data/image/avatar900x900.png"); @@ -261,6 +262,7 @@ mod tests { t.ctx .set_config(Config::Selfavatar, Some(&avatar_src.to_str().unwrap())) + .await .unwrap(); let avatar_cfg = t.ctx.get_config(Config::Selfavatar); assert_eq!(avatar_cfg, avatar_src.to_str().map(|s| s.to_string())); diff --git a/src/configure/mod.rs b/src/configure/mod.rs index c5240e551..3aaa1efa1 100644 --- a/src/configure/mod.rs +++ b/src/configure/mod.rs @@ -12,7 +12,7 @@ use crate::config::Config; use crate::constants::*; use crate::context::Context; use crate::dc_tools::*; -use crate::job::{self, job_add, job_kill_action}; +use crate::job; use crate::login_param::{CertificateChecks, LoginParam}; use crate::oauth2::*; use crate::param::Params; @@ -34,13 +34,13 @@ macro_rules! progress { impl Context { /// Starts a configuration job. - pub fn configure(&self) { + pub async fn configure(&self) { if self.has_ongoing() { warn!(self, "There is already another ongoing process running.",); return; } - job_kill_action(self, job::Action::ConfigureImap); - job_add(self, job::Action::ConfigureImap, 0, Params::new(), 0); + job::kill_action(self, job::Action::ConfigureImap).await; + job::add(self, job::Action::ConfigureImap, 0, Params::new(), 0).await; } /// Checks if the context is already configured. @@ -52,8 +52,8 @@ impl Context { /******************************************************************************* * Configure JOB ******************************************************************************/ -#[allow(non_snake_case, unused_must_use, clippy::cognitive_complexity)] -pub(crate) fn JobConfigureImap(context: &Context) -> job::Status { +#[allow(clippy::cognitive_complexity)] +pub(crate) async fn job_configure_imap(context: &Context) -> job::Status { if !context.sql.is_open() { error!(context, "Cannot configure, database not opened.",); progress!(context, 0); @@ -74,19 +74,22 @@ pub(crate) fn JobConfigureImap(context: &Context) -> job::Status { .read() .unwrap() .imap - .disconnect(context); + .disconnect(context) + .await; context .sentbox_thread .read() .unwrap() .imap - .disconnect(context); + .disconnect(context) + .await; context .mvbox_thread .read() .unwrap() .imap - .disconnect(context); + .disconnect(context) + .await; context.smtp.clone().lock().unwrap().disconnect(); info!(context, "Configure ...",); @@ -375,7 +378,7 @@ pub(crate) fn JobConfigureImap(context: &Context) -> job::Status { warn!(context, "configuring folders failed: {:?}", err); false } else { - let res = imap.select_with_uidvalidity(context, "INBOX"); + let res = imap.select_with_uidvalidity(context, "INBOX").await; if let Err(err) = res { error!(context, "could not read INBOX status: {:?}", err); false @@ -394,7 +397,10 @@ pub(crate) fn JobConfigureImap(context: &Context) -> job::Status { ) .ok(); - context.sql.set_raw_config_bool(context, "configured", true); + context + .sql + .set_raw_config_bool(context, "configured", true) + .ok(); true } 18 => { @@ -402,8 +408,7 @@ pub(crate) fn JobConfigureImap(context: &Context) -> job::Status { // we generate the keypair just now - we could also postpone this until the first message is sent, however, // this may result in a unexpected and annoying delay when the user sends his very first message // (~30 seconds on a Moto G4 play) and might looks as if message sending is always that slow. - e2ee::ensure_secret_key_exists(context); - success = true; + success = e2ee::ensure_secret_key_exists(context).is_ok(); info!(context, "key generation completed"); progress!(context, 940); break; // We are done here @@ -424,7 +429,8 @@ pub(crate) fn JobConfigureImap(context: &Context) -> job::Status { .read() .unwrap() .imap - .disconnect(context); + .disconnect(context) + .await; } if smtp_connected_here { context.smtp.clone().lock().unwrap().disconnect(); @@ -434,9 +440,13 @@ pub(crate) fn JobConfigureImap(context: &Context) -> job::Status { // and restore to last-entered on failure. // this way, the parameters visible to the ui are always in-sync with the current configuration. if success { - LoginParam::from_database(context, "").save_to_database(context, "configured_raw_"); + LoginParam::from_database(context, "") + .save_to_database(context, "configured_raw_") + .ok(); } else { - LoginParam::from_database(context, "configured_raw_").save_to_database(context, ""); + LoginParam::from_database(context, "configured_raw_") + .save_to_database(context, "") + .ok(); } if let Some(provider) = provider::get_provider_info(¶m.addr) { @@ -656,17 +666,20 @@ mod tests { use super::*; use crate::config::*; - use crate::configure::JobConfigureImap; use crate::test_utils::*; - #[test] - fn test_no_panic_on_bad_credentials() { + #[async_std::test] + async fn test_no_panic_on_bad_credentials() { let t = dummy_context(); t.ctx .set_config(Config::Addr, Some("probably@unexistant.addr")) + .await .unwrap(); - t.ctx.set_config(Config::MailPw, Some("123456")).unwrap(); - JobConfigureImap(&t.ctx); + t.ctx + .set_config(Config::MailPw, Some("123456")) + .await + .unwrap(); + job_configure_imap(&t.ctx).await; } #[test] diff --git a/src/contact.rs b/src/contact.rs index 6b4780228..b3d6672b3 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -1219,12 +1219,12 @@ mod tests { assert_eq!(contacts.len(), 0); } - #[test] - fn test_is_self_addr() -> Result<()> { + #[async_std::test] + async fn test_is_self_addr() -> Result<()> { let t = test_context(None); assert!(t.ctx.is_self_addr("me@me.org").is_err()); - let addr = configure_alice_keypair(&t.ctx); + let addr = configure_alice_keypair(&t.ctx).await; assert_eq!(t.ctx.is_self_addr("me@me.org")?, false); assert_eq!(t.ctx.is_self_addr(&addr)?, true); diff --git a/src/context.rs b/src/context.rs index a037e7a7f..ce98962ed 100644 --- a/src/context.rs +++ b/src/context.rs @@ -12,7 +12,7 @@ use crate::contact::*; use crate::error::*; use crate::events::Event; use crate::imap::*; -use crate::job::*; +use crate::job::{self, Action}; use crate::job_thread::JobThread; use crate::key::Key; use crate::login_param::LoginParam; @@ -423,7 +423,7 @@ impl Context { } } - pub fn do_heuristics_moves(&self, folder: &str, msg_id: MsgId) { + pub async fn do_heuristics_moves(&self, folder: &str, msg_id: MsgId) { if !self.get_config_bool(Config::MvboxMove) { return; } @@ -441,13 +441,14 @@ impl Context { match msg.is_dc_message { MessengerMessage::No => {} MessengerMessage::Yes | MessengerMessage::Reply => { - job_add( + job::add( self, Action::MoveMsg, msg.id.to_u32() as i32, Params::new(), 0, - ); + ) + .await; } } } @@ -456,15 +457,32 @@ impl Context { impl Drop for Context { fn drop(&mut self) { - info!(self, "disconnecting inbox-thread",); - self.inbox_thread.read().unwrap().imap.disconnect(self); - info!(self, "disconnecting sentbox-thread",); - self.sentbox_thread.read().unwrap().imap.disconnect(self); - info!(self, "disconnecting mvbox-thread",); - self.mvbox_thread.read().unwrap().imap.disconnect(self); - info!(self, "disconnecting SMTP"); - self.smtp.clone().lock().unwrap().disconnect(); - self.sql.close(self); + async_std::task::block_on(async move { + info!(self, "disconnecting inbox-thread"); + self.inbox_thread + .read() + .unwrap() + .imap + .disconnect(self) + .await; + info!(self, "disconnecting sentbox-thread"); + self.sentbox_thread + .read() + .unwrap() + .imap + .disconnect(self) + .await; + info!(self, "disconnecting mvbox-thread"); + self.mvbox_thread + .read() + .unwrap() + .imap + .disconnect(self) + .await; + info!(self, "disconnecting SMTP"); + self.smtp.clone().lock().unwrap().disconnect(); + self.sql.close(self); + }); } } diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index d65107540..095dc359f 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -1,7 +1,6 @@ use itertools::join; -use sha2::{Digest, Sha256}; - use num_traits::FromPrimitive; +use sha2::{Digest, Sha256}; use crate::chat::{self, Chat, ChatId}; use crate::config::Config; @@ -12,7 +11,7 @@ use crate::dc_tools::*; use crate::error::Result; use crate::events::Event; use crate::headerdef::HeaderDef; -use crate::job::*; +use crate::job::{self, Action}; use crate::message::{self, MessageState, MessengerMessage, MsgId}; use crate::mimeparser::*; use crate::param::*; @@ -32,7 +31,7 @@ enum CreateEvent { } /// Receive a message and add it to the database. -pub fn dc_receive_imf( +pub async fn dc_receive_imf( context: &Context, imf_raw: &[u8], server_folder: impl AsRef, @@ -158,7 +157,9 @@ pub fn dc_receive_imf( &mut insert_msg_id, &mut created_db_entries, &mut create_event_to_send, - ) { + ) + .await + { cleanup(context, &create_event_to_send, created_db_entries); bail!("add_parts error: {:?}", err); } @@ -194,15 +195,18 @@ pub fn dc_receive_imf( // if we delete we don't need to try moving messages if needs_delete_job && !created_db_entries.is_empty() { - job_add( + job::add( context, Action::DeleteMsgOnImap, created_db_entries[0].1.to_u32() as i32, Params::new(), 0, - ); + ) + .await; } else { - context.do_heuristics_moves(server_folder.as_ref(), insert_msg_id); + context + .do_heuristics_moves(server_folder.as_ref(), insert_msg_id) + .await; } info!( @@ -212,7 +216,9 @@ pub fn dc_receive_imf( cleanup(context, &create_event_to_send, created_db_entries); - mime_parser.handle_reports(context, from_id, sent_timestamp, &server_folder, server_uid); + mime_parser + .handle_reports(context, from_id, sent_timestamp, &server_folder, server_uid) + .await; Ok(()) } @@ -259,7 +265,7 @@ pub fn from_field_to_contact_id( } #[allow(clippy::too_many_arguments, clippy::cognitive_complexity)] -fn add_parts( +async fn add_parts( context: &Context, mut mime_parser: &mut MimeMessage, imf_raw: &[u8], @@ -347,7 +353,7 @@ fn add_parts( msgrmsg = MessengerMessage::Yes; *chat_id = ChatId::new(0); allow_creation = true; - match handle_securejoin_handshake(context, mime_parser, from_id) { + match handle_securejoin_handshake(context, mime_parser, from_id).await { Ok(securejoin::HandshakeMessage::Done) => { *hidden = true; *needs_delete_job = true; diff --git a/src/e2ee.rs b/src/e2ee.rs index 55baf2acb..940d0793b 100644 --- a/src/e2ee.rs +++ b/src/e2ee.rs @@ -367,10 +367,10 @@ mod tests { mod ensure_secret_key_exists { use super::*; - #[test] - fn test_prexisting() { + #[async_std::test] + async fn test_prexisting() { let t = dummy_context(); - let test_addr = configure_alice_keypair(&t.ctx); + let test_addr = configure_alice_keypair(&t.ctx).await; assert_eq!(ensure_secret_key_exists(&t.ctx).unwrap(), test_addr); } @@ -408,10 +408,10 @@ Sent with my Delta Chat Messenger: https://delta.chat"; mod load_or_generate_self_public_key { use super::*; - #[test] - fn test_existing() { + #[async_std::test] + async fn test_existing() { let t = dummy_context(); - let addr = configure_alice_keypair(&t.ctx); + let addr = configure_alice_keypair(&t.ctx).await; let key = load_or_generate_self_public_key(&t.ctx, addr); assert!(key.is_ok()); } diff --git a/src/imap/idle.rs b/src/imap/idle.rs index 86f0f292c..537f0fb40 100644 --- a/src/imap/idle.rs +++ b/src/imap/idle.rs @@ -65,235 +65,228 @@ impl Imap { task::block_on(async move { self.config.read().await.can_idle }) } - pub fn idle(&self, context: &Context, watch_folder: Option) -> Result<()> { - task::block_on(async move { - if !self.can_idle() { - return Err(Error::IdleAbilityMissing); - } + pub async fn idle(&self, context: &Context, watch_folder: Option) -> Result<()> { + if !self.can_idle() { + return Err(Error::IdleAbilityMissing); + } - self.setup_handle_if_needed(context) - .await - .map_err(Error::SetupHandleError)?; + self.setup_handle_if_needed(context) + .await + .map_err(Error::SetupHandleError)?; - self.select_folder(context, watch_folder.clone()).await?; + self.select_folder(context, watch_folder.clone()).await?; - let session = self.session.lock().await.take(); - let timeout = Duration::from_secs(23 * 60); - if let Some(session) = session { - match session.idle() { - // BEWARE: If you change the Secure branch you - // typically also need to change the Insecure branch. - IdleHandle::Secure(mut handle) => { - if let Err(err) = handle.init().await { - return Err(Error::IdleProtocolFailed(err)); - } + let session = self.session.lock().await.take(); + let timeout = Duration::from_secs(23 * 60); + if let Some(session) = session { + match session.idle() { + // BEWARE: If you change the Secure branch you + // typically also need to change the Insecure branch. + IdleHandle::Secure(mut handle) => { + if let Err(err) = handle.init().await { + return Err(Error::IdleProtocolFailed(err)); + } - let (idle_wait, interrupt) = handle.wait_with_timeout(timeout); - *self.interrupt.lock().await = Some(interrupt); + let (idle_wait, interrupt) = handle.wait_with_timeout(timeout); + *self.interrupt.lock().await = Some(interrupt); - if self.skip_next_idle_wait.load(Ordering::SeqCst) { - // interrupt_idle has happened before we - // provided self.interrupt - self.skip_next_idle_wait.store(false, Ordering::SeqCst); - std::mem::drop(idle_wait); - info!(context, "Idle wait was skipped"); - } else { - info!(context, "Idle entering wait-on-remote state"); - match idle_wait.await { - Ok(IdleResponse::NewData(_)) => { - info!(context, "Idle has NewData"); - } - // TODO: idle_wait does not distinguish manual interrupts - // from Timeouts if we would know it's a Timeout we could bail - // directly and reconnect . - Ok(IdleResponse::Timeout) => { - info!(context, "Idle-wait timeout or interruption"); - } - Ok(IdleResponse::ManualInterrupt) => { - info!(context, "Idle wait was interrupted"); - } - Err(err) => { - warn!(context, "Idle wait errored: {:?}", err); - } + if self.skip_next_idle_wait.load(Ordering::SeqCst) { + // interrupt_idle has happened before we + // provided self.interrupt + self.skip_next_idle_wait.store(false, Ordering::SeqCst); + std::mem::drop(idle_wait); + info!(context, "Idle wait was skipped"); + } else { + info!(context, "Idle entering wait-on-remote state"); + match idle_wait.await { + Ok(IdleResponse::NewData(_)) => { + info!(context, "Idle has NewData"); } - } - // if we can't properly terminate the idle - // protocol let's break the connection. - let res = - async_std::future::timeout(Duration::from_secs(15), handle.done()) - .await - .map_err(|err| { - self.trigger_reconnect(); - Error::IdleTimeout(err) - })?; - - match res { - Ok(session) => { - *self.session.lock().await = Some(Session::Secure(session)); + // TODO: idle_wait does not distinguish manual interrupts + // from Timeouts if we would know it's a Timeout we could bail + // directly and reconnect . + Ok(IdleResponse::Timeout) => { + info!(context, "Idle-wait timeout or interruption"); + } + Ok(IdleResponse::ManualInterrupt) => { + info!(context, "Idle wait was interrupted"); } Err(err) => { - // if we cannot terminate IDLE it probably - // means that we waited long (with idle_wait) - // but the network went away/changed - self.trigger_reconnect(); - return Err(Error::IdleProtocolFailed(err)); + warn!(context, "Idle wait errored: {:?}", err); } } } - IdleHandle::Insecure(mut handle) => { - if let Err(err) = handle.init().await { + // if we can't properly terminate the idle + // protocol let's break the connection. + let res = async_std::future::timeout(Duration::from_secs(15), handle.done()) + .await + .map_err(|err| { + self.trigger_reconnect(); + Error::IdleTimeout(err) + })?; + + match res { + Ok(session) => { + *self.session.lock().await = Some(Session::Secure(session)); + } + Err(err) => { + // if we cannot terminate IDLE it probably + // means that we waited long (with idle_wait) + // but the network went away/changed + self.trigger_reconnect(); return Err(Error::IdleProtocolFailed(err)); } + } + } + IdleHandle::Insecure(mut handle) => { + if let Err(err) = handle.init().await { + return Err(Error::IdleProtocolFailed(err)); + } - let (idle_wait, interrupt) = handle.wait_with_timeout(timeout); - *self.interrupt.lock().await = Some(interrupt); + let (idle_wait, interrupt) = handle.wait_with_timeout(timeout); + *self.interrupt.lock().await = Some(interrupt); - if self.skip_next_idle_wait.load(Ordering::SeqCst) { - // interrupt_idle has happened before we - // provided self.interrupt - self.skip_next_idle_wait.store(false, Ordering::SeqCst); - std::mem::drop(idle_wait); - info!(context, "Idle wait was skipped"); - } else { - info!(context, "Idle entering wait-on-remote state"); - match idle_wait.await { - Ok(IdleResponse::NewData(_)) => { - info!(context, "Idle has NewData"); - } - // TODO: idle_wait does not distinguish manual interrupts - // from Timeouts if we would know it's a Timeout we could bail - // directly and reconnect . - Ok(IdleResponse::Timeout) => { - info!(context, "Idle-wait timeout or interruption"); - } - Ok(IdleResponse::ManualInterrupt) => { - info!(context, "Idle wait was interrupted"); - } - Err(err) => { - warn!(context, "Idle wait errored: {:?}", err); - } + if self.skip_next_idle_wait.load(Ordering::SeqCst) { + // interrupt_idle has happened before we + // provided self.interrupt + self.skip_next_idle_wait.store(false, Ordering::SeqCst); + std::mem::drop(idle_wait); + info!(context, "Idle wait was skipped"); + } else { + info!(context, "Idle entering wait-on-remote state"); + match idle_wait.await { + Ok(IdleResponse::NewData(_)) => { + info!(context, "Idle has NewData"); } - } - // if we can't properly terminate the idle - // protocol let's break the connection. - let res = - async_std::future::timeout(Duration::from_secs(15), handle.done()) - .await - .map_err(|err| { - self.trigger_reconnect(); - Error::IdleTimeout(err) - })?; - - match res { - Ok(session) => { - *self.session.lock().await = Some(Session::Insecure(session)); + // TODO: idle_wait does not distinguish manual interrupts + // from Timeouts if we would know it's a Timeout we could bail + // directly and reconnect . + Ok(IdleResponse::Timeout) => { + info!(context, "Idle-wait timeout or interruption"); + } + Ok(IdleResponse::ManualInterrupt) => { + info!(context, "Idle wait was interrupted"); } Err(err) => { - // if we cannot terminate IDLE it probably - // means that we waited long (with idle_wait) - // but the network went away/changed - self.trigger_reconnect(); - return Err(Error::IdleProtocolFailed(err)); + warn!(context, "Idle wait errored: {:?}", err); } } } + // if we can't properly terminate the idle + // protocol let's break the connection. + let res = async_std::future::timeout(Duration::from_secs(15), handle.done()) + .await + .map_err(|err| { + self.trigger_reconnect(); + Error::IdleTimeout(err) + })?; + + match res { + Ok(session) => { + *self.session.lock().await = Some(Session::Insecure(session)); + } + Err(err) => { + // if we cannot terminate IDLE it probably + // means that we waited long (with idle_wait) + // but the network went away/changed + self.trigger_reconnect(); + return Err(Error::IdleProtocolFailed(err)); + } + } } } + } - Ok(()) - }) + Ok(()) } - pub(crate) fn fake_idle(&self, context: &Context, watch_folder: Option) { + pub(crate) async fn fake_idle(&self, context: &Context, watch_folder: Option) { // Idle using polling. This is also needed if we're not yet configured - // in this case, we're waiting for a configure job (and an interrupt). - task::block_on(async move { - let fake_idle_start_time = SystemTime::now(); - info!(context, "IMAP-fake-IDLEing..."); + let fake_idle_start_time = SystemTime::now(); - let interrupt = stop_token::StopSource::new(); + info!(context, "IMAP-fake-IDLEing..."); - // check every minute if there are new messages - // TODO: grow sleep durations / make them more flexible - let interval = async_std::stream::interval(Duration::from_secs(60)); - let mut interrupt_interval = interrupt.stop_token().stop_stream(interval); - *self.interrupt.lock().await = Some(interrupt); - if self.skip_next_idle_wait.load(Ordering::SeqCst) { - // interrupt_idle has happened before we - // provided self.interrupt - self.skip_next_idle_wait.store(false, Ordering::SeqCst); - info!(context, "fake-idle wait was skipped"); - } else { - // loop until we are interrupted or if we fetched something - while let Some(_) = interrupt_interval.next().await { - // try to connect with proper login params - // (setup_handle_if_needed might not know about them if we - // never successfully connected) - if let Err(err) = self.connect_configured(context) { - warn!(context, "fake_idle: could not connect: {}", err); - continue; - } - if self.config.read().await.can_idle { - // we only fake-idled because network was gone during IDLE, probably - break; - } - info!(context, "fake_idle is connected"); - // we are connected, let's see if fetching messages results - // in anything. If so, we behave as if IDLE had data but - // will have already fetched the messages so perform_*_fetch - // will not find any new. + let interrupt = stop_token::StopSource::new(); - if let Some(ref watch_folder) = watch_folder { - match self.fetch_new_messages(context, watch_folder).await { - Ok(res) => { - info!(context, "fetch_new_messages returned {:?}", res); - if res { - break; - } - } - Err(err) => { - error!(context, "could not fetch from folder: {}", err); - self.trigger_reconnect() + // check every minute if there are new messages + // TODO: grow sleep durations / make them more flexible + let interval = async_std::stream::interval(Duration::from_secs(60)); + let mut interrupt_interval = interrupt.stop_token().stop_stream(interval); + *self.interrupt.lock().await = Some(interrupt); + if self.skip_next_idle_wait.load(Ordering::SeqCst) { + // interrupt_idle has happened before we + // provided self.interrupt + self.skip_next_idle_wait.store(false, Ordering::SeqCst); + info!(context, "fake-idle wait was skipped"); + } else { + // loop until we are interrupted or if we fetched something + while let Some(_) = interrupt_interval.next().await { + // try to connect with proper login params + // (setup_handle_if_needed might not know about them if we + // never successfully connected) + if let Err(err) = self.connect_configured(context).await { + warn!(context, "fake_idle: could not connect: {}", err); + continue; + } + if self.config.read().await.can_idle { + // we only fake-idled because network was gone during IDLE, probably + break; + } + info!(context, "fake_idle is connected"); + // we are connected, let's see if fetching messages results + // in anything. If so, we behave as if IDLE had data but + // will have already fetched the messages so perform_*_fetch + // will not find any new. + + if let Some(ref watch_folder) = watch_folder { + match self.fetch_new_messages(context, watch_folder).await { + Ok(res) => { + info!(context, "fetch_new_messages returned {:?}", res); + if res { + break; } } + Err(err) => { + error!(context, "could not fetch from folder: {}", err); + self.trigger_reconnect() + } } } } - self.interrupt.lock().await.take(); + } + self.interrupt.lock().await.take(); - info!( - context, - "IMAP-fake-IDLE done after {:.4}s", - SystemTime::now() - .duration_since(fake_idle_start_time) - .unwrap_or_default() - .as_millis() as f64 - / 1000., - ); - }) + info!( + context, + "IMAP-fake-IDLE done after {:.4}s", + SystemTime::now() + .duration_since(fake_idle_start_time) + .unwrap_or_default() + .as_millis() as f64 + / 1000., + ); } - pub fn interrupt_idle(&self, context: &Context) { - task::block_on(async move { - let mut interrupt: Option = self.interrupt.lock().await.take(); - if interrupt.is_none() { - // idle wait is not running, signal it needs to skip - self.skip_next_idle_wait.store(true, Ordering::SeqCst); + pub async fn interrupt_idle(&self, context: &Context) { + let mut interrupt: Option = self.interrupt.lock().await.take(); + if interrupt.is_none() { + // idle wait is not running, signal it needs to skip + self.skip_next_idle_wait.store(true, Ordering::SeqCst); - // meanwhile idle-wait may have produced the StopSource - interrupt = self.interrupt.lock().await.take(); - } - // let's manually drop the StopSource - if interrupt.is_some() { - // the imap thread provided us a stop token but might - // not have entered idle_wait yet, give it some time - // for that to happen. XXX handle this without extra wait - // https://github.com/deltachat/deltachat-core-rust/issues/925 - std::thread::sleep(Duration::from_millis(200)); - info!(context, "low-level: dropping stop-source to interrupt idle"); - std::mem::drop(interrupt) - } - }); + // meanwhile idle-wait may have produced the StopSource + interrupt = self.interrupt.lock().await.take(); + } + // let's manually drop the StopSource + if interrupt.is_some() { + // the imap thread provided us a stop token but might + // not have entered idle_wait yet, give it some time + // for that to happen. XXX handle this without extra wait + // https://github.com/deltachat/deltachat-core-rust/issues/925 + std::thread::sleep(Duration::from_millis(200)); + info!(context, "low-level: dropping stop-source to interrupt idle"); + std::mem::drop(interrupt) + } } } diff --git a/src/imap/mod.rs b/src/imap/mod.rs index 834120e0e..ea62f64e2 100644 --- a/src/imap/mod.rs +++ b/src/imap/mod.rs @@ -22,7 +22,7 @@ use crate::dc_receive_imf::{ }; use crate::events::Event; use crate::headerdef::{HeaderDef, HeaderDefMap}; -use crate::job::{job_add, Action}; +use crate::job::{self, Action}; use crate::login_param::{CertificateChecks, LoginParam}; use crate::message::{self, update_server_uid}; use crate::oauth2::dc_get_oauth2_access_token; @@ -363,8 +363,8 @@ impl Imap { } /// Connects to imap account using already-configured parameters. - pub fn connect_configured(&self, context: &Context) -> Result<()> { - if async_std::task::block_on(self.is_connected()) && !self.should_reconnect() { + pub async fn connect_configured(&self, context: &Context) -> Result<()> { + if self.is_connected().await && !self.should_reconnect() { return Ok(()); } if !context.sql.get_raw_config_bool(context, "configured") { @@ -374,7 +374,7 @@ impl Imap { let param = LoginParam::from_database(context, "configured_"); // the trailing underscore is correct - if task::block_on(self.connect(context, ¶m)) { + if self.connect(context, ¶m).await { self.ensure_configured_folders(context, true) } else { Err(Error::ConnectionFailed(format!("{}", param))) @@ -451,7 +451,7 @@ impl Imap { }; if teardown { - self.disconnect(context); + self.disconnect(context).await; false } else { @@ -459,11 +459,9 @@ impl Imap { } } - pub fn disconnect(&self, context: &Context) { - task::block_on(async move { - self.unsetup_handle(context).await; - self.free_connect_params().await; - }); + pub async fn disconnect(&self, context: &Context) { + self.unsetup_handle(context).await; + self.free_connect_params().await; } pub async fn fetch(&self, context: &Context, watch_folder: &str) -> Result<()> { @@ -502,85 +500,83 @@ impl Imap { } /// return Result with (uid_validity, last_seen_uid) tuple. - pub(crate) fn select_with_uidvalidity( + pub(crate) async fn select_with_uidvalidity( &self, context: &Context, folder: &str, ) -> Result<(u32, u32)> { - task::block_on(async move { - self.select_folder(context, Some(folder)).await?; + self.select_folder(context, Some(folder)).await?; - // compare last seen UIDVALIDITY against the current one - let (uid_validity, last_seen_uid) = self.get_config_last_seen_uid(context, &folder); + // compare last seen UIDVALIDITY against the current one + let (uid_validity, last_seen_uid) = self.get_config_last_seen_uid(context, &folder); - let config = self.config.read().await; - let mailbox = config - .selected_mailbox - .as_ref() - .ok_or_else(|| Error::NoMailbox(folder.to_string()))?; + let config = self.config.read().await; + let mailbox = config + .selected_mailbox + .as_ref() + .ok_or_else(|| Error::NoMailbox(folder.to_string()))?; - let new_uid_validity = match mailbox.uid_validity { - Some(v) => v, - None => { - let s = format!("No UIDVALIDITY for folder {:?}", folder); - return Err(Error::Other(s)); - } - }; - - if new_uid_validity == uid_validity { - return Ok((uid_validity, last_seen_uid)); + let new_uid_validity = match mailbox.uid_validity { + Some(v) => v, + None => { + let s = format!("No UIDVALIDITY for folder {:?}", folder); + return Err(Error::Other(s)); } + }; - if mailbox.exists == 0 { - info!(context, "Folder \"{}\" is empty.", folder); + if new_uid_validity == uid_validity { + return Ok((uid_validity, last_seen_uid)); + } - // set lastseenuid=0 for empty folders. - // id we do not do this here, we'll miss the first message - // as we will get in here again and fetch from lastseenuid+1 then + if mailbox.exists == 0 { + info!(context, "Folder \"{}\" is empty.", folder); - self.set_config_last_seen_uid(context, &folder, new_uid_validity, 0); - return Ok((new_uid_validity, 0)); + // set lastseenuid=0 for empty folders. + // id we do not do this here, we'll miss the first message + // as we will get in here again and fetch from lastseenuid+1 then + + self.set_config_last_seen_uid(context, &folder, new_uid_validity, 0); + return Ok((new_uid_validity, 0)); + } + + // uid_validity has changed or is being set the first time. + // find the last seen uid within the new uid_validity scope. + let new_last_seen_uid = match mailbox.uid_next { + Some(uid_next) => { + uid_next - 1 // XXX could uid_next be 0? } - - // uid_validity has changed or is being set the first time. - // find the last seen uid within the new uid_validity scope. - let new_last_seen_uid = match mailbox.uid_next { - Some(uid_next) => { - uid_next - 1 // XXX could uid_next be 0? - } - None => { - warn!( - context, - "IMAP folder has no uid_next, fall back to fetching" - ); - if let Some(ref mut session) = &mut *self.session.lock().await { - // note that we use fetch by sequence number - // and thus we only need to get exactly the - // last-index message. - let set = format!("{}", mailbox.exists); - match session.fetch(set, JUST_UID).await { - Ok(list) => list[0].uid.unwrap_or_default(), - Err(err) => { - return Err(Error::FetchFailed(err)); - } + None => { + warn!( + context, + "IMAP folder has no uid_next, fall back to fetching" + ); + if let Some(ref mut session) = &mut *self.session.lock().await { + // note that we use fetch by sequence number + // and thus we only need to get exactly the + // last-index message. + let set = format!("{}", mailbox.exists); + match session.fetch(set, JUST_UID).await { + Ok(list) => list[0].uid.unwrap_or_default(), + Err(err) => { + return Err(Error::FetchFailed(err)); } - } else { - return Err(Error::NoConnection); } + } else { + return Err(Error::NoConnection); } - }; + } + }; - self.set_config_last_seen_uid(context, &folder, new_uid_validity, new_last_seen_uid); - info!( - context, - "uid/validity change: new {}/{} current {}/{}", - new_last_seen_uid, - new_uid_validity, - uid_validity, - last_seen_uid - ); - Ok((new_uid_validity, new_last_seen_uid)) - }) + self.set_config_last_seen_uid(context, &folder, new_uid_validity, new_last_seen_uid); + info!( + context, + "uid/validity change: new {}/{} current {}/{}", + new_last_seen_uid, + new_uid_validity, + uid_validity, + last_seen_uid + ); + Ok((new_uid_validity, new_last_seen_uid)) } async fn fetch_new_messages>( @@ -591,10 +587,11 @@ impl Imap { let show_emails = ShowEmails::from_i32(context.get_config_int(Config::ShowEmails)).unwrap_or_default(); - let (uid_validity, last_seen_uid) = - self.select_with_uidvalidity(context, folder.as_ref())?; + let (uid_validity, last_seen_uid) = self + .select_with_uidvalidity(context, folder.as_ref()) + .await?; - let mut read_cnt = 0; + let mut read_cnt: usize = 0; let mut list = if let Some(ref mut session) = &mut *self.session.lock().await { // fetch messages with larger UID than the last one seen @@ -636,7 +633,7 @@ impl Imap { let headers = get_fetch_headers(fetch)?; let message_id = prefetch_get_message_id(&headers).unwrap_or_default(); - if precheck_imf(context, &message_id, folder.as_ref(), cur_uid) { + if precheck_imf(context, &message_id, folder.as_ref(), cur_uid).await { // we know the message-id already or don't want the message otherwise. info!( context, @@ -763,7 +760,7 @@ impl Imap { if !is_deleted && msg.body().is_some() { let body = msg.body().unwrap_or_default(); if let Err(err) = - dc_receive_imf(context, &body, folder.as_ref(), server_uid, is_seen) + dc_receive_imf(context, &body, folder.as_ref(), server_uid, is_seen).await { warn!( context, @@ -786,11 +783,11 @@ impl Imap { Ok(()) } - pub fn can_move(&self) -> bool { - task::block_on(async move { self.config.read().await.can_move }) + pub async fn can_move(&self) -> bool { + self.config.read().await.can_move } - pub fn mv( + pub async fn mv( &self, context: &Context, folder: &str, @@ -798,96 +795,94 @@ impl Imap { dest_folder: &str, dest_uid: &mut u32, ) -> ImapActionResult { - task::block_on(async move { - if folder == dest_folder { - info!( - context, - "Skip moving message; message {}/{} is already in {}...", - folder, - uid, - dest_folder, - ); - return ImapActionResult::AlreadyDone; - } - if let Some(imapresult) = self.prepare_imap_operation_on_msg(context, folder, uid) { - return imapresult; - } - // we are connected, and the folder is selected + if folder == dest_folder { + info!( + context, + "Skip moving message; message {}/{} is already in {}...", folder, uid, dest_folder, + ); + return ImapActionResult::AlreadyDone; + } + if let Some(imapresult) = self + .prepare_imap_operation_on_msg(context, folder, uid) + .await + { + return imapresult; + } + // we are connected, and the folder is selected - // XXX Rust-Imap provides no target uid on mv, so just set it to 0 - *dest_uid = 0; + // XXX Rust-Imap provides no target uid on mv, so just set it to 0 + *dest_uid = 0; - let set = format!("{}", uid); - let display_folder_id = format!("{}/{}", folder, uid); - - if self.can_move() { - if let Some(ref mut session) = &mut *self.session.lock().await { - match session.uid_mv(&set, &dest_folder).await { - Ok(_) => { - emit_event!( - context, - Event::ImapMessageMoved(format!( - "IMAP Message {} moved to {}", - display_folder_id, dest_folder - )) - ); - return ImapActionResult::Success; - } - Err(err) => { - warn!( - context, - "Cannot move message, fallback to COPY/DELETE {}/{} to {}: {}", - folder, - uid, - dest_folder, - err - ); - } - } - } else { - unreachable!(); - }; - } else { - info!( - context, - "Server does not support MOVE, fallback to COPY/DELETE {}/{} to {}", - folder, - uid, - dest_folder - ); - } + let set = format!("{}", uid); + let display_folder_id = format!("{}/{}", folder, uid); + if self.can_move().await { if let Some(ref mut session) = &mut *self.session.lock().await { - if let Err(err) = session.uid_copy(&set, &dest_folder).await { - warn!(context, "Could not copy message: {}", err); - return ImapActionResult::Failed; + match session.uid_mv(&set, &dest_folder).await { + Ok(_) => { + emit_event!( + context, + Event::ImapMessageMoved(format!( + "IMAP Message {} moved to {}", + display_folder_id, dest_folder + )) + ); + return ImapActionResult::Success; + } + Err(err) => { + warn!( + context, + "Cannot move message, fallback to COPY/DELETE {}/{} to {}: {}", + folder, + uid, + dest_folder, + err + ); + } } } else { unreachable!(); - } + }; + } else { + info!( + context, + "Server does not support MOVE, fallback to COPY/DELETE {}/{} to {}", + folder, + uid, + dest_folder + ); + } - if !self.add_flag_finalized(context, uid, "\\Deleted").await { - warn!(context, "Cannot mark {} as \"Deleted\" after copy.", uid); - emit_event!( - context, - Event::ImapMessageMoved(format!( - "IMAP Message {} copied to {} (delete FAILED)", - display_folder_id, dest_folder - )) - ); - ImapActionResult::Failed - } else { - self.config.write().await.selected_folder_needs_expunge = true; - emit_event!( - context, - Event::ImapMessageMoved(format!( - "IMAP Message {} copied to {} (delete successfull)", - display_folder_id, dest_folder - )) - ); - ImapActionResult::Success + if let Some(ref mut session) = &mut *self.session.lock().await { + if let Err(err) = session.uid_copy(&set, &dest_folder).await { + warn!(context, "Could not copy message: {}", err); + return ImapActionResult::Failed; } - }) + } else { + unreachable!(); + } + + if !self.add_flag_finalized(context, uid, "\\Deleted").await { + warn!(context, "Cannot mark {} as \"Deleted\" after copy.", uid); + emit_event!( + context, + Event::ImapMessageMoved(format!( + "IMAP Message {} copied to {} (delete FAILED)", + display_folder_id, dest_folder + )) + ); + ImapActionResult::Failed + } else { + self.config.write().await.selected_folder_needs_expunge = true; + emit_event!( + context, + Event::ImapMessageMoved(format!( + "IMAP Message {} copied to {} (delete successfull)", + display_folder_id, dest_folder + )) + ); + ImapActionResult::Success + } } async fn add_flag_finalized(&self, context: &Context, server_uid: u32, flag: &str) -> bool { @@ -929,51 +924,52 @@ impl Imap { } } - pub fn prepare_imap_operation_on_msg( + pub async fn prepare_imap_operation_on_msg( &self, context: &Context, folder: &str, uid: u32, ) -> Option { - task::block_on(async move { - if uid == 0 { - return Some(ImapActionResult::Failed); + if uid == 0 { + return Some(ImapActionResult::Failed); + } + if !self.is_connected().await { + // currently jobs are only performed on the INBOX thread + // TODO: make INBOX/SENT/MVBOX perform the jobs on their + // respective folders to avoid select_folder network traffic + // and the involved error states + if let Err(err) = self.connect_configured(context).await { + warn!(context, "prepare_imap_op failed: {}", err); + return Some(ImapActionResult::RetryLater); } - if !self.is_connected().await { - // currently jobs are only performed on the INBOX thread - // TODO: make INBOX/SENT/MVBOX perform the jobs on their - // respective folders to avoid select_folder network traffic - // and the involved error states - if let Err(err) = self.connect_configured(context) { - warn!(context, "prepare_imap_op failed: {}", err); - return Some(ImapActionResult::RetryLater); - } + } + match self.select_folder(context, Some(&folder)).await { + Ok(()) => None, + Err(select_folder::Error::ConnectionLost) => { + warn!(context, "Lost imap connection"); + Some(ImapActionResult::RetryLater) } - match self.select_folder(context, Some(&folder)).await { - Ok(()) => None, - Err(select_folder::Error::ConnectionLost) => { - warn!(context, "Lost imap connection"); - Some(ImapActionResult::RetryLater) - } - Err(select_folder::Error::NoSession) => { - warn!(context, "no imap session"); - Some(ImapActionResult::Failed) - } - Err(select_folder::Error::BadFolderName(folder_name)) => { - warn!(context, "invalid folder name: {:?}", folder_name); - Some(ImapActionResult::Failed) - } - Err(err) => { - warn!(context, "failed to select folder: {:?}: {:?}", folder, err); - Some(ImapActionResult::RetryLater) - } + Err(select_folder::Error::NoSession) => { + warn!(context, "no imap session"); + Some(ImapActionResult::Failed) } - }) + Err(select_folder::Error::BadFolderName(folder_name)) => { + warn!(context, "invalid folder name: {:?}", folder_name); + Some(ImapActionResult::Failed) + } + Err(err) => { + warn!(context, "failed to select folder: {:?}: {:?}", folder, err); + Some(ImapActionResult::RetryLater) + } + } } pub fn set_seen(&self, context: &Context, folder: &str, uid: u32) -> ImapActionResult { task::block_on(async move { - if let Some(imapresult) = self.prepare_imap_operation_on_msg(context, folder, uid) { + if let Some(imapresult) = self + .prepare_imap_operation_on_msg(context, folder, uid) + .await + { return imapresult; } // we are connected, and the folder is selected @@ -999,7 +995,10 @@ impl Imap { uid: &mut u32, ) -> ImapActionResult { task::block_on(async move { - if let Some(imapresult) = self.prepare_imap_operation_on_msg(context, folder, *uid) { + if let Some(imapresult) = self + .prepare_imap_operation_on_msg(context, folder, *uid) + .await + { return imapresult; } // we are connected, and the folder is selected @@ -1291,20 +1290,28 @@ fn get_folder_meaning(folder_name: &Name) -> FolderMeaning { } } -fn precheck_imf(context: &Context, rfc724_mid: &str, server_folder: &str, server_uid: u32) -> bool { +async fn precheck_imf( + context: &Context, + rfc724_mid: &str, + server_folder: &str, + server_uid: u32, +) -> bool { if let Ok((old_server_folder, old_server_uid, msg_id)) = message::rfc724_mid_exists(context, &rfc724_mid) { if old_server_folder.is_empty() && old_server_uid == 0 { info!(context, "[move] detected bcc-self {}", rfc724_mid,); - context.do_heuristics_moves(server_folder.as_ref(), msg_id); - job_add( + context + .do_heuristics_moves(server_folder.as_ref(), msg_id) + .await; + job::add( context, Action::MarkseenMsgOnImap, msg_id.to_u32() as i32, Params::new(), 0, - ); + ) + .await; } else if old_server_folder != server_folder { info!(context, "[move] detected moved message {}", rfc724_mid,); } diff --git a/src/imex.rs b/src/imex.rs index ce603961e..8dcf408db 100644 --- a/src/imex.rs +++ b/src/imex.rs @@ -16,7 +16,7 @@ use crate::dc_tools::*; use crate::e2ee; use crate::error::*; use crate::events::Event; -use crate::job::*; +use crate::job::{self, Action, Job}; use crate::key::{self, Key}; use crate::message::{Message, MsgId}; use crate::mimeparser::SystemMessage; @@ -69,15 +69,15 @@ pub enum ImexMode { /// /// Only one import-/export-progress can run at the same time. /// To cancel an import-/export-progress, use dc_stop_ongoing_process(). -pub fn imex(context: &Context, what: ImexMode, param1: Option>) { +pub async fn imex(context: &Context, what: ImexMode, param1: Option>) { let mut param = Params::new(); param.set_int(Param::Cmd, what as i32); if let Some(param1) = param1 { param.set(Param::Arg, param1.as_ref().to_string_lossy()); } - job_kill_action(context, Action::ImexImap); - job_add(context, Action::ImexImap, 0, param, 0); + job::kill_action(context, Action::ImexImap).await; + job::add(context, Action::ImexImap, 0, param, 0).await; } /// Returns the filename of the backup found (otherwise an error) @@ -113,14 +113,14 @@ pub fn has_backup(context: &Context, dir_name: impl AsRef) -> Result Result { +pub async fn initiate_key_transfer(context: &Context) -> Result { ensure!(context.alloc_ongoing(), "could not allocate ongoing"); - let res = do_initiate_key_transfer(context); + let res = do_initiate_key_transfer(context).await; context.free_ongoing(); res } -fn do_initiate_key_transfer(context: &Context) -> Result { +async fn do_initiate_key_transfer(context: &Context) -> Result { let mut msg: Message; let setup_code = create_setup_code(context); /* this may require a keypair to be created. this may take a second ... */ @@ -148,7 +148,7 @@ fn do_initiate_key_transfer(context: &Context) -> Result { ); ensure!(!context.shall_stop_ongoing(), "canceled"); - let msg_id = chat::send_msg(context, chat_id, &mut msg)?; + let msg_id = chat::send_msg(context, chat_id, &mut msg).await?; info!(context, "Wait for setup message being sent ...",); while !context.shall_stop_ongoing() { std::thread::sleep(std::time::Duration::from_secs(1)); @@ -740,11 +740,11 @@ mod tests { use crate::test_utils::*; use ::pgp::armor::BlockType; - #[test] - fn test_render_setup_file() { + #[async_std::test] + async fn test_render_setup_file() { let t = test_context(Some(Box::new(logging_cb))); - configure_alice_keypair(&t.ctx); + configure_alice_keypair(&t.ctx).await; let msg = render_setup_file(&t.ctx, "hello").unwrap(); println!("{}", &msg); // Check some substrings, indicating things got substituted. @@ -760,13 +760,13 @@ mod tests { assert!(msg.contains("-----END PGP MESSAGE-----\n")); } - #[test] - fn test_render_setup_file_newline_replace() { + #[async_std::test] + async fn test_render_setup_file_newline_replace() { let t = dummy_context(); t.ctx .set_stock_translation(StockMessage::AcSetupMsgBody, "hello\r\nthere".to_string()) .unwrap(); - configure_alice_keypair(&t.ctx); + configure_alice_keypair(&t.ctx).await; let msg = render_setup_file(&t.ctx, "pw").unwrap(); println!("{}", &msg); assert!(msg.contains("

hello
there

")); diff --git a/src/job.rs b/src/job.rs index 23405ea41..bbda38062 100644 --- a/src/job.rs +++ b/src/job.rs @@ -3,14 +3,13 @@ //! This module implements a job queue maintained in the SQLite database //! and job types. +use std::future::Future; use std::{fmt, time}; use deltachat_derive::{FromSql, ToSql}; use itertools::Itertools; use rand::{thread_rng, Rng}; -use async_std::task; - use crate::blob::BlobObject; use crate::chat::{self, ChatId}; use crate::config::Config; @@ -23,6 +22,7 @@ use crate::error::{Error, Result}; use crate::events::Event; use crate::imap::*; use crate::imex::*; +use crate::job; use crate::location; use crate::login_param::LoginParam; use crate::message::MsgId; @@ -55,8 +55,8 @@ pub enum Status { macro_rules! job_try { ($expr:expr) => { match $expr { - ::std::result::Result::Ok(val) => val, - ::std::result::Result::Err(err) => { + std::result::Result::Ok(val) => val, + std::result::Result::Err(err) => { return $crate::job::Status::Finished(Err(err.into())); } } @@ -144,7 +144,7 @@ impl fmt::Display for Job { impl Job { /// Deletes the job from the database. - fn delete(&self, context: &Context) -> bool { + async fn delete(&self, context: &Context) -> bool { context .sql .execute("DELETE FROM jobs WHERE id=?;", params![self.job_id as i32]) @@ -154,7 +154,7 @@ impl Job { /// Updates the job already stored in the database. /// /// To add a new job, use [job_add]. - fn update(&self, context: &Context) -> bool { + async fn update(&self, context: &Context) -> bool { sql::execute( context, &context.sql, @@ -169,7 +169,7 @@ impl Job { .is_ok() } - fn smtp_send( + async fn smtp_send( &mut self, context: &Context, recipients: Vec, @@ -178,7 +178,8 @@ impl Job { success_cb: F, ) -> Status where - F: FnOnce() -> Result<()>, + F: FnOnce() -> Fut, + Fut: Future>, { // hold the smtp lock during sending of a job and // its ok/error response processing. Note that if a message @@ -189,7 +190,7 @@ impl Job { info!(context, "smtp-sending out mime message:"); println!("{}", String::from_utf8_lossy(&message)); } - match task::block_on(smtp.send(context, recipients, message, job_id)) { + match smtp.send(context, recipients, message, job_id).await { Err(crate::smtp::send::Error::SendError(err)) => { // Remote error, retry later. warn!(context, "SMTP failed to send: {}", err); @@ -232,15 +233,14 @@ impl Job { Status::Finished(Err(format_err!("SMTP has not transport"))) } Ok(()) => { - job_try!(success_cb()); + job_try!(success_cb().await); Status::Finished(Ok(())) } } } - #[allow(non_snake_case)] - fn SendMsgToSmtp(&mut self, context: &Context) -> Status { - /* connect to SMTP server, if not yet done */ + async fn send_msg_to_smtp(&mut self, context: &Context) -> Status { + // connect to SMTP server, if not yet done if !context.smtp.lock().unwrap().is_connected() { let loginparam = LoginParam::from_database(context, "configured_"); if let Err(err) = context.smtp.lock().unwrap().connect(context, &loginparam) { @@ -285,18 +285,21 @@ impl Job { let foreign_id = self.foreign_id; self.smtp_send(context, recipients_list, body, self.job_id, || { - // smtp success, update db ASAP, then delete smtp file - if 0 != foreign_id { - set_delivered(context, MsgId::new(foreign_id)); + async move { + // smtp success, update db ASAP, then delete smtp file + if 0 != foreign_id { + set_delivered(context, MsgId::new(foreign_id)); + } + // now also delete the generated file + dc_delete_file(context, filename); + Ok(()) } - // now also delete the generated file - dc_delete_file(context, filename); - Ok(()) }) + .await } /// Get `SendMdn` jobs with foreign_id equal to `contact_id` excluding the `job_id` job. - fn get_additional_mdn_jobs( + async fn get_additional_mdn_jobs( &self, context: &Context, contact_id: u32, @@ -335,8 +338,7 @@ impl Job { Ok((job_ids, rfc724_mids)) } - #[allow(non_snake_case)] - fn SendMdn(&mut self, context: &Context) -> Status { + async fn send_mdn(&mut self, context: &Context) -> Status { if !context.get_config_bool(Config::MdnsEnabled) { // User has disabled MDNs after job scheduling but before // execution. @@ -361,6 +363,7 @@ impl Job { // Try to aggregate other SendMdn jobs and send a combined MDN. let (additional_job_ids, additional_rfc724_mids) = self .get_additional_mdn_jobs(context, contact_id) + .await .unwrap_or_default(); if !additional_rfc724_mids.is_empty() { @@ -391,14 +394,16 @@ impl Job { } self.smtp_send(context, recipients, body, self.job_id, || { - // Remove additional SendMdn jobs we have aggregated into this one. - job_kill_ids(context, &additional_job_ids)?; - Ok(()) + async move { + // Remove additional SendMdn jobs we have aggregated into this one. + job::kill_ids(context, &additional_job_ids).await?; + Ok(()) + } }) + .await } - #[allow(non_snake_case)] - fn MoveMsg(&mut self, context: &Context) -> Status { + async fn move_msg(&mut self, context: &Context) -> Status { let imap_inbox = &context.inbox_thread.read().unwrap().imap; let msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id))); @@ -415,13 +420,16 @@ impl Job { let server_folder = msg.server_folder.as_ref().unwrap(); let mut dest_uid = 0; - match imap_inbox.mv( - context, - server_folder, - msg.server_uid, - &dest_folder, - &mut dest_uid, - ) { + match imap_inbox + .mv( + context, + server_folder, + msg.server_uid, + &dest_folder, + &mut dest_uid, + ) + .await + { ImapActionResult::RetryLater => Status::RetryLater, ImapActionResult::Success => { message::update_server_uid(context, &msg.rfc724_mid, &dest_folder, dest_uid); @@ -437,8 +445,7 @@ impl Job { } } - #[allow(non_snake_case)] - fn DeleteMsgOnImap(&mut self, context: &Context) -> Status { + async fn delete_msg_on_imap(&mut self, context: &Context) -> Status { let imap_inbox = &context.inbox_thread.read().unwrap().imap; let mut msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id))); @@ -468,8 +475,7 @@ impl Job { } } - #[allow(non_snake_case)] - fn EmptyServer(&mut self, context: &Context) -> Status { + async fn empty_server(&mut self, context: &Context) -> Status { let imap_inbox = &context.inbox_thread.read().unwrap().imap; if self.foreign_id & DC_EMPTY_MVBOX > 0 { if let Some(mvbox_folder) = context @@ -485,8 +491,7 @@ impl Job { Status::Finished(Ok(())) } - #[allow(non_snake_case)] - fn MarkseenMsgOnImap(&mut self, context: &Context) -> Status { + async fn markseen_msg_on_imap(&mut self, context: &Context) -> Status { let imap_inbox = &context.inbox_thread.read().unwrap().imap; let msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id))); @@ -503,7 +508,7 @@ impl Job { if msg.param.get_bool(Param::WantsMdn).unwrap_or_default() && context.get_config_bool(Config::MdnsEnabled) { - if let Err(err) = send_mdn(context, &msg) { + if let Err(err) = send_mdn(context, &msg).await { warn!(context, "could not send out mdn for {}: {}", msg.id, err); return Status::Finished(Err(err)); } @@ -513,8 +518,7 @@ impl Job { } } - #[allow(non_snake_case)] - fn MarkseenMdnOnImap(&mut self, context: &Context) -> Status { + async fn markseen_mdn_on_imap(&mut self, context: &Context) -> Status { let folder = self .param .get(Param::ServerFolder) @@ -536,7 +540,9 @@ impl Job { if let Some(dest_folder) = dest_folder { let mut dest_uid = 0; if ImapActionResult::RetryLater - == imap_inbox.mv(context, &folder, uid, &dest_folder, &mut dest_uid) + == imap_inbox + .mv(context, &folder, uid, &dest_folder, &mut dest_uid) + .await { Status::RetryLater } else { @@ -551,8 +557,8 @@ impl Job { } } -/* delete all pending jobs with the given action */ -pub fn job_kill_action(context: &Context, action: Action) -> bool { +/// Delete all pending jobs with the given action. +pub async fn kill_action(context: &Context, action: Action) -> bool { sql::execute( context, &context.sql, @@ -563,7 +569,7 @@ pub fn job_kill_action(context: &Context, action: Action) -> bool { } /// Remove jobs with specified IDs. -pub fn job_kill_ids(context: &Context, job_ids: &[u32]) -> sql::Result<()> { +pub async fn kill_ids(context: &Context, job_ids: &[u32]) -> sql::Result<()> { sql::execute( context, &context.sql, @@ -575,43 +581,40 @@ pub fn job_kill_ids(context: &Context, job_ids: &[u32]) -> sql::Result<()> { ) } -pub fn perform_inbox_fetch(context: &Context) { +pub async fn perform_inbox_fetch(context: &Context) { let use_network = context.get_config_bool(Config::InboxWatch); - task::block_on( - context - .inbox_thread - .write() - .unwrap() - .fetch(context, use_network), - ); + context + .inbox_thread + .write() + .unwrap() + .fetch(context, use_network) + .await; } -pub fn perform_mvbox_fetch(context: &Context) { +pub async fn perform_mvbox_fetch(context: &Context) { let use_network = context.get_config_bool(Config::MvboxWatch); - task::block_on( - context - .mvbox_thread - .write() - .unwrap() - .fetch(context, use_network), - ); + context + .mvbox_thread + .write() + .unwrap() + .fetch(context, use_network) + .await; } -pub fn perform_sentbox_fetch(context: &Context) { +pub async fn perform_sentbox_fetch(context: &Context) { let use_network = context.get_config_bool(Config::SentboxWatch); - task::block_on( - context - .sentbox_thread - .write() - .unwrap() - .fetch(context, use_network), - ); + context + .sentbox_thread + .write() + .unwrap() + .fetch(context, use_network) + .await; } -pub fn perform_inbox_idle(context: &Context) { +pub async fn perform_inbox_idle(context: &Context) { if *context.perform_inbox_jobs_needed.clone().read().unwrap() { info!( context, @@ -625,30 +628,33 @@ pub fn perform_inbox_idle(context: &Context) { .inbox_thread .read() .unwrap() - .idle(context, use_network); + .idle(context, use_network) + .await; } -pub fn perform_mvbox_idle(context: &Context) { +pub async fn perform_mvbox_idle(context: &Context) { let use_network = context.get_config_bool(Config::MvboxWatch); context .mvbox_thread .read() .unwrap() - .idle(context, use_network); + .idle(context, use_network) + .await; } -pub fn perform_sentbox_idle(context: &Context) { +pub async fn perform_sentbox_idle(context: &Context) { let use_network = context.get_config_bool(Config::SentboxWatch); context .sentbox_thread .read() .unwrap() - .idle(context, use_network); + .idle(context, use_network) + .await; } -pub fn interrupt_inbox_idle(context: &Context) { +pub async fn interrupt_inbox_idle(context: &Context) { info!(context, "interrupt_inbox_idle called"); // we do not block on trying to obtain the thread lock // because we don't know in which state the thread is. @@ -656,7 +662,7 @@ pub fn interrupt_inbox_idle(context: &Context) { // but we flag it for checking jobs so that idle will be skipped. match context.inbox_thread.try_read() { Ok(inbox_thread) => { - inbox_thread.interrupt_idle(context); + inbox_thread.interrupt_idle(context).await; } Err(err) => { *context.perform_inbox_jobs_needed.write().unwrap() = true; @@ -665,19 +671,25 @@ pub fn interrupt_inbox_idle(context: &Context) { } } -pub fn interrupt_mvbox_idle(context: &Context) { - context.mvbox_thread.read().unwrap().interrupt_idle(context); +pub async fn interrupt_mvbox_idle(context: &Context) { + context + .mvbox_thread + .read() + .unwrap() + .interrupt_idle(context) + .await; } -pub fn interrupt_sentbox_idle(context: &Context) { +pub async fn interrupt_sentbox_idle(context: &Context) { context .sentbox_thread .read() .unwrap() - .interrupt_idle(context); + .interrupt_idle(context) + .await; } -pub fn perform_smtp_jobs(context: &Context) { +pub async fn perform_smtp_jobs(context: &Context) { let probe_smtp_network = { let &(ref lock, _) = &*context.smtp_state.clone(); let mut state = lock.lock().unwrap(); @@ -695,7 +707,7 @@ pub fn perform_smtp_jobs(context: &Context) { }; info!(context, "SMTP-jobs started...",); - job_perform(context, Thread::Smtp, probe_smtp_network); + job_perform(context, Thread::Smtp, probe_smtp_network).await; info!(context, "SMTP-jobs ended."); { @@ -706,7 +718,7 @@ pub fn perform_smtp_jobs(context: &Context) { } } -pub fn perform_smtp_idle(context: &Context) { +pub async fn perform_smtp_idle(context: &Context) { info!(context, "SMTP-idle started...",); { let &(ref lock, ref cvar) = &*context.smtp_state.clone(); @@ -762,7 +774,7 @@ fn get_next_wakeup_time(context: &Context, thread: Thread) -> time::Duration { wakeup_time } -pub fn maybe_network(context: &Context) { +pub async fn maybe_network(context: &Context) { { let &(ref lock, _) = &*context.smtp_state.clone(); let mut state = lock.lock().unwrap(); @@ -771,13 +783,13 @@ pub fn maybe_network(context: &Context) { *context.probe_imap_network.write().unwrap() = true; } - interrupt_smtp_idle(context); - interrupt_inbox_idle(context); - interrupt_mvbox_idle(context); - interrupt_sentbox_idle(context); + interrupt_smtp_idle(context).await; + interrupt_inbox_idle(context).await; + interrupt_mvbox_idle(context).await; + interrupt_sentbox_idle(context).await; } -pub fn job_action_exists(context: &Context, action: Action) -> bool { +pub fn action_exists(context: &Context, action: Action) -> bool { context .sql .exists("SELECT id FROM jobs WHERE action=?;", params![action]) @@ -797,8 +809,8 @@ fn set_delivered(context: &Context, msg_id: MsgId) { context.call_cb(Event::MsgDelivered { chat_id, msg_id }); } -/* special case for DC_JOB_SEND_MSG_TO_SMTP */ -pub fn job_send_msg(context: &Context, msg_id: MsgId) -> Result<()> { +// special case for DC_JOB_SEND_MSG_TO_SMTP +pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> { let mut msg = Message::load_from_db(context, msg_id)?; msg.try_calc_and_set_dimensions(context).ok(); @@ -892,31 +904,32 @@ pub fn job_send_msg(context: &Context, msg_id: MsgId) -> Result<()> { msg.id, recipients, &rendered_msg, - )?; + ) + .await?; Ok(()) } -pub fn perform_inbox_jobs(context: &Context) { +pub async fn perform_inbox_jobs(context: &Context) { info!(context, "dc_perform_inbox_jobs starting.",); let probe_imap_network = *context.probe_imap_network.clone().read().unwrap(); *context.probe_imap_network.write().unwrap() = false; *context.perform_inbox_jobs_needed.write().unwrap() = false; - job_perform(context, Thread::Imap, probe_imap_network); + job_perform(context, Thread::Imap, probe_imap_network).await; info!(context, "dc_perform_inbox_jobs ended.",); } -pub fn perform_mvbox_jobs(context: &Context) { - info!(context, "dc_perform_mbox_jobs EMPTY (for now).",); +pub async fn perform_mvbox_jobs(context: &Context) { + info!(context, "dc_perform_mbox_jobs EMPTY (for now)."); } -pub fn perform_sentbox_jobs(context: &Context) { - info!(context, "dc_perform_sentbox_jobs EMPTY (for now).",); +pub async fn perform_sentbox_jobs(context: &Context) { + info!(context, "dc_perform_sentbox_jobs EMPTY (for now)."); } -fn job_perform(context: &Context, thread: Thread, probe_network: bool) { +async fn job_perform(context: &Context, thread: Thread, probe_network: bool) { while let Some(mut job) = load_next_job(context, thread, probe_network) { info!(context, "{}-job {} started...", thread, job); @@ -925,24 +938,26 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) { // - they may change the database handle; we do not keep old pointers therefore // - they can be re-executed one time AT_ONCE, but they are not saved in the database for later execution if Action::ConfigureImap == job.action || Action::ImexImap == job.action { - job_kill_action(context, job.action); + job::kill_action(context, job.action).await; context .sentbox_thread .clone() .read() .unwrap() - .suspend(context); + .suspend(context) + .await; context .mvbox_thread .clone() .read() .unwrap() - .suspend(context); + .suspend(context) + .await; suspend_smtp_thread(context, true); } - let try_res = match perform_job_action(context, &mut job, thread, 0) { - Status::RetryNow => perform_job_action(context, &mut job, thread, 1), + let try_res = match perform_job_action(context, &mut job, thread, 0).await { + Status::RetryNow => perform_job_action(context, &mut job, thread, 1).await, x => x, }; @@ -975,7 +990,7 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) { job.tries = tries; let time_offset = get_backoff_time_offset(tries); job.desired_timestamp = time() + time_offset; - job.update(context); + job.update(context).await; info!( context, "{}-job #{} not succeeded on try #{}, retry in {} seconds.", @@ -1008,7 +1023,7 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) { job.pending_error.as_ref(), ); } - job.delete(context); + job.delete(context).await; } if !probe_network { continue; @@ -1029,13 +1044,18 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) { info!(context, "{} removes job {} as it succeeded", thread, job); } - job.delete(context); + job.delete(context).await; } } } } -fn perform_job_action(context: &Context, mut job: &mut Job, thread: Thread, tries: u32) -> Status { +async fn perform_job_action( + context: &Context, + mut job: &mut Job, + thread: Thread, + tries: u32, +) -> Status { info!( context, "{} begin immediate try {} of job {}", thread, tries, job @@ -1043,14 +1063,14 @@ fn perform_job_action(context: &Context, mut job: &mut Job, thread: Thread, trie let try_res = match job.action { Action::Unknown => Status::Finished(Err(format_err!("Unknown job id found"))), - Action::SendMsgToSmtp => job.SendMsgToSmtp(context), - Action::EmptyServer => job.EmptyServer(context), - Action::DeleteMsgOnImap => job.DeleteMsgOnImap(context), - Action::MarkseenMsgOnImap => job.MarkseenMsgOnImap(context), - Action::MarkseenMdnOnImap => job.MarkseenMdnOnImap(context), - Action::MoveMsg => job.MoveMsg(context), - Action::SendMdn => job.SendMdn(context), - Action::ConfigureImap => JobConfigureImap(context), + Action::SendMsgToSmtp => job.send_msg_to_smtp(context).await, + Action::EmptyServer => job.empty_server(context).await, + Action::DeleteMsgOnImap => job.delete_msg_on_imap(context).await, + Action::MarkseenMsgOnImap => job.markseen_msg_on_imap(context).await, + Action::MarkseenMdnOnImap => job.markseen_mdn_on_imap(context).await, + Action::MoveMsg => job.move_msg(context).await, + Action::SendMdn => job.send_mdn(context).await, + Action::ConfigureImap => job_configure_imap(context).await, Action::ImexImap => match JobImexImap(context, &job) { Ok(()) => Status::Finished(Ok(())), Err(err) => { @@ -1058,7 +1078,7 @@ fn perform_job_action(context: &Context, mut job: &mut Job, thread: Thread, trie Status::Finished(Err(err)) } }, - Action::MaybeSendLocations => location::JobMaybeSendLocations(context, &job), + Action::MaybeSendLocations => location::job_maybe_send_locations(context, &job).await, Action::MaybeSendLocationsEnded => location::JobMaybeSendLocationsEnded(context, &mut job), Action::Housekeeping => { sql::housekeeping(context); @@ -1097,16 +1117,16 @@ fn suspend_smtp_thread(context: &Context, suspend: bool) { } } -fn send_mdn(context: &Context, msg: &Message) -> Result<()> { +async fn send_mdn(context: &Context, msg: &Message) -> Result<()> { let mut param = Params::new(); param.set(Param::MsgId, msg.id.to_u32().to_string()); - job_add(context, Action::SendMdn, msg.from_id as i32, param, 0); + job::add(context, Action::SendMdn, msg.from_id as i32, param, 0).await; Ok(()) } -fn add_smtp_job( +async fn add_smtp_job( context: &Context, action: Action, msg_id: MsgId, @@ -1122,14 +1142,13 @@ fn add_smtp_job( param.set(Param::File, blob.as_name()); param.set(Param::Recipients, &recipients); - job_add(context, action, msg_id.to_u32() as i32, param, 0); + add(context, action, msg_id.to_u32() as i32, param, 0).await; Ok(()) } -/// Adds a job to the database, scheduling it `delay_seconds` -/// after the current time. -pub fn job_add( +/// Adds a job to the database, scheduling it `delay_seconds` after the current time. +pub async fn add( context: &Context, action: Action, foreign_id: i32, @@ -1159,13 +1178,13 @@ pub fn job_add( ).ok(); match thread { - Thread::Imap => interrupt_inbox_idle(context), - Thread::Smtp => interrupt_smtp_idle(context), + Thread::Imap => interrupt_inbox_idle(context).await, + Thread::Smtp => interrupt_smtp_idle(context).await, Thread::Unknown => {} } } -pub fn interrupt_smtp_idle(context: &Context) { +pub async fn interrupt_smtp_idle(context: &Context) { info!(context, "Interrupting SMTP-idle...",); let &(ref lock, ref cvar) = &*context.smtp_state.clone(); diff --git a/src/job_thread.rs b/src/job_thread.rs index 7cf51353c..c36beb757 100644 --- a/src/job_thread.rs +++ b/src/job_thread.rs @@ -30,12 +30,12 @@ impl JobThread { } } - pub fn suspend(&self, context: &Context) { + pub async fn suspend(&self, context: &Context) { info!(context, "Suspending {}-thread.", self.name,); { self.state.0.lock().unwrap().suspended = true; } - self.interrupt_idle(context); + self.interrupt_idle(context).await; loop { let using_handle = self.state.0.lock().unwrap().using_handle; if !using_handle { @@ -56,14 +56,14 @@ impl JobThread { cvar.notify_one(); } - pub fn interrupt_idle(&self, context: &Context) { + pub async fn interrupt_idle(&self, context: &Context) { { self.state.0.lock().unwrap().jobs_needed = true; } info!(context, "Interrupting {}-IDLE...", self.name); - self.imap.interrupt_idle(context); + self.imap.interrupt_idle(context).await; let &(ref lock, ref cvar) = &*self.state.clone(); let mut state = lock.lock().unwrap(); @@ -99,7 +99,7 @@ impl JobThread { async fn connect_and_fetch(&mut self, context: &Context) -> Result<()> { let prefix = format!("{}-fetch", self.name); - match self.imap.connect_configured(context) { + match self.imap.connect_configured(context).await { Ok(()) => { if let Some(watch_folder) = self.get_watch_folder(context) { let start = std::time::Instant::now(); @@ -135,7 +135,7 @@ impl JobThread { } } - pub fn idle(&self, context: &Context, use_network: bool) { + pub async fn idle(&self, context: &Context, use_network: bool) { { let &(ref lock, ref cvar) = &*self.state.clone(); let mut state = lock.lock().unwrap(); @@ -172,19 +172,19 @@ impl JobThread { } let prefix = format!("{}-IDLE", self.name); - let do_fake_idle = match self.imap.connect_configured(context) { + let do_fake_idle = match self.imap.connect_configured(context).await { Ok(()) => { if !self.imap.can_idle() { true // we have to do fake_idle } else { let watch_folder = self.get_watch_folder(context); info!(context, "{} started...", prefix); - let res = self.imap.idle(context, watch_folder); + let res = self.imap.idle(context, watch_folder).await; info!(context, "{} ended...", prefix); if let Err(err) = res { warn!(context, "{} failed: {} -> reconnecting", prefix, err); // something is borked, let's start afresh on the next occassion - self.imap.disconnect(context); + self.imap.disconnect(context).await; } false } @@ -199,7 +199,7 @@ impl JobThread { }; if do_fake_idle { let watch_folder = self.get_watch_folder(context); - self.imap.fake_idle(context, watch_folder); + self.imap.fake_idle(context, watch_folder).await; } self.state.0.lock().unwrap().using_handle = false; diff --git a/src/location.rs b/src/location.rs index 4f8072278..b494957ae 100644 --- a/src/location.rs +++ b/src/location.rs @@ -11,7 +11,7 @@ use crate::context::*; use crate::dc_tools::*; use crate::error::Error; use crate::events::Event; -use crate::job::{self, job_action_exists, job_add, Job}; +use crate::job::{self, Job}; use crate::message::{Message, MsgId}; use crate::mimeparser::SystemMessage; use crate::param::*; @@ -192,7 +192,7 @@ impl Kml { } // location streaming -pub fn send_locations_to_chat(context: &Context, chat_id: ChatId, seconds: i64) { +pub async fn send_locations_to_chat(context: &Context, chat_id: ChatId, seconds: i64) { let now = time(); if !(seconds < 0 || chat_id.is_special()) { let is_sending_locations_before = is_sending_locations_to_chat(context, chat_id); @@ -216,7 +216,9 @@ pub fn send_locations_to_chat(context: &Context, chat_id: ChatId, seconds: i64) msg.text = Some(context.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0)); msg.param.set_cmd(SystemMessage::LocationStreamingEnabled); - chat::send_msg(context, chat_id, &mut msg).unwrap_or_default(); + chat::send_msg(context, chat_id, &mut msg) + .await + .unwrap_or_default(); } else if 0 == seconds && is_sending_locations_before { let stock_str = context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0); @@ -224,29 +226,30 @@ pub fn send_locations_to_chat(context: &Context, chat_id: ChatId, seconds: i64) } context.call_cb(Event::ChatModified(chat_id)); if 0 != seconds { - schedule_MAYBE_SEND_LOCATIONS(context, false); - job_add( + schedule_maybe_send_locations(context, false).await; + job::add( context, job::Action::MaybeSendLocationsEnded, chat_id.to_u32() as i32, Params::new(), seconds + 1, - ); + ) + .await; } } } } -#[allow(non_snake_case)] -fn schedule_MAYBE_SEND_LOCATIONS(context: &Context, force_schedule: bool) { - if force_schedule || !job_action_exists(context, job::Action::MaybeSendLocations) { - job_add( +async fn schedule_maybe_send_locations(context: &Context, force_schedule: bool) { + if force_schedule || !job::action_exists(context, job::Action::MaybeSendLocations) { + job::add( context, job::Action::MaybeSendLocations, 0, Params::new(), 60, - ); + ) + .await; }; } @@ -260,7 +263,7 @@ pub fn is_sending_locations_to_chat(context: &Context, chat_id: ChatId) -> bool .unwrap_or_default() } -pub fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> bool { +pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> bool { if latitude == 0.0 && longitude == 0.0 { return true; } @@ -293,7 +296,7 @@ pub fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> b if continue_streaming { context.call_cb(Event::LocationChanged(Some(DC_CONTACT_ID_SELF))); }; - schedule_MAYBE_SEND_LOCATIONS(context, false); + schedule_maybe_send_locations(context, false).await; } continue_streaming @@ -547,8 +550,7 @@ pub fn save( .map_err(Into::into) } -#[allow(non_snake_case)] -pub(crate) fn JobMaybeSendLocations(context: &Context, _job: &Job) -> job::Status { +pub(crate) async fn job_maybe_send_locations(context: &Context, _job: &Job) -> job::Status { let now = time(); let mut continue_streaming = false; info!( @@ -629,11 +631,13 @@ pub(crate) fn JobMaybeSendLocations(context: &Context, _job: &Job) -> job::Statu for (chat_id, mut msg) in msgs.into_iter() { // TODO: better error handling - chat::send_msg(context, chat_id, &mut msg).unwrap_or_default(); + chat::send_msg(context, chat_id, &mut msg) + .await + .unwrap_or_default(); } } if continue_streaming { - schedule_MAYBE_SEND_LOCATIONS(context, true); + schedule_maybe_send_locations(context, true).await; } job::Status::Finished(Ok(())) } diff --git a/src/message.rs b/src/message.rs index 67d8c2d23..b2a7c7e21 100644 --- a/src/message.rs +++ b/src/message.rs @@ -14,7 +14,7 @@ use crate::context::*; use crate::dc_tools::*; use crate::error::Error; use crate::events::Event; -use crate::job::*; +use crate::job::{self, Action}; use crate::lot::{Lot, LotState, Meaning}; use crate::mimeparser::SystemMessage; use crate::param::*; @@ -950,7 +950,7 @@ pub fn get_mime_headers(context: &Context, msg_id: MsgId) -> Option { ) } -pub fn delete_msgs(context: &Context, msg_ids: &[MsgId]) { +pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) { for msg_id in msg_ids.iter() { if let Ok(msg) = Message::load_from_db(context, *msg_id) { if msg.location_id > 0 { @@ -958,13 +958,14 @@ pub fn delete_msgs(context: &Context, msg_ids: &[MsgId]) { } } update_msg_chat_id(context, *msg_id, ChatId::new(DC_CHAT_ID_TRASH)); - job_add( + job::add( context, Action::DeleteMsgOnImap, msg_id.to_u32() as i32, Params::new(), 0, - ); + ) + .await; } if !msg_ids.is_empty() { @@ -972,9 +973,9 @@ pub fn delete_msgs(context: &Context, msg_ids: &[MsgId]) { chat_id: ChatId::new(0), msg_id: MsgId::new(0), }); - job_kill_action(context, Action::Housekeeping); - job_add(context, Action::Housekeeping, 0, Params::new(), 10); - }; + job::kill_action(context, Action::Housekeeping).await; + job::add(context, Action::Housekeeping, 0, Params::new(), 10).await; + } } fn update_msg_chat_id(context: &Context, msg_id: MsgId, chat_id: ChatId) -> bool { @@ -997,7 +998,7 @@ fn delete_poi_location(context: &Context, location_id: u32) -> bool { .is_ok() } -pub fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool { +pub async fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool { if msg_ids.is_empty() { return false; } @@ -1044,13 +1045,14 @@ pub fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool { update_msg_state(context, *id, MessageState::InSeen); info!(context, "Seen message {}.", id); - job_add( + job::add( context, Action::MarkseenMsgOnImap, id.to_u32() as i32, Params::new(), 0, - ); + ) + .await; send_event = true; } } else if curr_state == MessageState::InFresh { @@ -1402,9 +1404,9 @@ pub fn update_server_uid( } #[allow(dead_code)] -pub fn dc_empty_server(context: &Context, flags: u32) { - job_kill_action(context, Action::EmptyServer); - job_add(context, Action::EmptyServer, flags as i32, Params::new(), 0); +pub async fn dc_empty_server(context: &Context, flags: u32) { + job::kill_action(context, Action::EmptyServer).await; + job::add(context, Action::EmptyServer, flags as i32, Params::new(), 0).await; } #[cfg(test)] @@ -1420,8 +1422,8 @@ mod tests { ); } - #[test] - pub fn test_prepare_message_and_send() { + #[async_std::test] + async fn test_prepare_message_and_send() { use crate::config::Config; let d = test::dummy_context(); @@ -1430,7 +1432,9 @@ mod tests { let contact = Contact::create(ctx, "", "dest@example.com").expect("failed to create contact"); - let res = ctx.set_config(Config::ConfiguredAddr, Some("self@example.com")); + let res = ctx + .set_config(Config::ConfiguredAddr, Some("self@example.com")) + .await; assert!(res.is_ok()); let chat = chat::create_by_contact_id(ctx, contact).unwrap(); diff --git a/src/mimeparser.rs b/src/mimeparser.rs index e4ab0817c..c95863f69 100644 --- a/src/mimeparser.rs +++ b/src/mimeparser.rs @@ -17,7 +17,7 @@ use crate::e2ee; use crate::error::Result; use crate::events::Event; use crate::headerdef::{HeaderDef, HeaderDefMap}; -use crate::job::{job_add, Action}; +use crate::job::{self, Action}; use crate::location; use crate::message; use crate::param::*; @@ -807,7 +807,7 @@ impl MimeMessage { } /// Handle reports (only MDNs for now) - pub fn handle_reports( + pub async fn handle_reports( &self, context: &Context, from_id: u32, @@ -840,7 +840,7 @@ impl MimeMessage { if self.has_chat_version() && context.get_config_bool(Config::MvboxMove) { param.set_int(Param::AlsoMove, 1); } - job_add(context, Action::MarkseenMdnOnImap, 0, param, 0); + job::add(context, Action::MarkseenMdnOnImap, 0, param, 0).await; } } } diff --git a/src/qr.rs b/src/qr.rs index 15e4cbc69..1dc35b50f 100644 --- a/src/qr.rs +++ b/src/qr.rs @@ -216,7 +216,7 @@ struct CreateAccountResponse { /// take a qr of the type DC_QR_ACCOUNT, parse it's parameters, /// download additional information from the contained url and set the parameters. /// on success, a configure::configure() should be able to log in to the account -pub fn set_config_from_qr(context: &Context, qr: &str) -> Result<(), Error> { +pub async fn set_config_from_qr(context: &Context, qr: &str) -> Result<(), Error> { let url_str = &qr[DCACCOUNT_SCHEME.len()..]; let response = reqwest::blocking::Client::new().post(url_str).send(); @@ -246,8 +246,12 @@ pub fn set_config_from_qr(context: &Context, qr: &str) -> Result<(), Error> { println!("response: {:?}", &parsed); let parsed = parsed.unwrap(); - context.set_config(Config::Addr, Some(&parsed.email))?; - context.set_config(Config::MailPw, Some(&parsed.password))?; + context + .set_config(Config::Addr, Some(&parsed.email)) + .await?; + context + .set_config(Config::MailPw, Some(&parsed.password)) + .await?; Ok(()) } diff --git a/src/securejoin.rs b/src/securejoin.rs index 28a8f0f7f..44f045842 100644 --- a/src/securejoin.rs +++ b/src/securejoin.rs @@ -145,7 +145,7 @@ fn get_self_fingerprint(context: &Context) -> Option { /// Take a scanned QR-code and do the setup-contact/join-group handshake. /// See the ffi-documentation for more details. -pub fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId { +pub async fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId { let cleanup = |context: &Context, contact_chat_id: ChatId, ongoing_allocated: bool, join_vg: bool| { let mut bob = context.bob.write().unwrap(); @@ -244,7 +244,8 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId { } else { "".to_string() }, - ); + ) + .await; } else { context.bob.write().unwrap().expects = DC_VC_AUTH_REQUIRED; @@ -256,7 +257,8 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId { get_qr_attr!(context, invitenumber), None, "", - ); + ) + .await; } if join_vg { @@ -273,7 +275,7 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId { } } -fn send_handshake_msg( +async fn send_handshake_msg( context: &Context, contact_chat_id: ChatId, step: &str, @@ -309,7 +311,9 @@ fn send_handshake_msg( msg.param.set_int(Param::GuaranteeE2ee, 1); } // TODO. handle cleanup on error - chat::send_msg(context, contact_chat_id, &mut msg).unwrap_or_default(); + chat::send_msg(context, contact_chat_id, &mut msg) + .await + .unwrap_or_default(); } fn chat_id_2_contact_id(context: &Context, contact_chat_id: ChatId) -> u32 { @@ -388,7 +392,7 @@ pub(crate) enum HandshakeMessage { /// When handle_securejoin_handshake() is called, /// the message is not yet filed in the database; /// this is done by receive_imf() later on as needed. -pub(crate) fn handle_securejoin_handshake( +pub(crate) async fn handle_securejoin_handshake( context: &Context, mime_message: &MimeMessage, contact_id: u32, @@ -459,7 +463,8 @@ pub(crate) fn handle_securejoin_handshake( "", None, "", - ); + ) + .await; Ok(HandshakeMessage::Done) } "vg-auth-required" | "vc-auth-required" => { @@ -526,7 +531,8 @@ pub(crate) fn handle_securejoin_handshake( } else { "".to_string() }, - ); + ) + .await; Ok(HandshakeMessage::Done) } "vg-request-with-auth" | "vc-request-with-auth" => { @@ -609,6 +615,7 @@ pub(crate) fn handle_securejoin_handshake( Ok((group_chat_id, _, _)) => { if let Err(err) = chat::add_contact_to_chat_ex(context, group_chat_id, contact_id, true) + .await { error!(context, "failed to add contact: {}", err); } @@ -622,7 +629,8 @@ pub(crate) fn handle_securejoin_handshake( } } else { // Alice -> Bob - send_handshake_msg(context, contact_chat_id, "vc-contact-confirm", "", None, ""); + send_handshake_msg(context, contact_chat_id, "vc-contact-confirm", "", None, "") + .await; inviter_progress!(context, contact_id, 1000); } Ok(HandshakeMessage::Done) @@ -719,7 +727,8 @@ pub(crate) fn handle_securejoin_handshake( "", None, "", - ); + ) + .await; } context.bob.write().unwrap().status = 1; context.stop_ongoing(); diff --git a/src/stock.rs b/src/stock.rs index 6d4775a85..b67e8b220 100644 --- a/src/stock.rs +++ b/src/stock.rs @@ -540,15 +540,15 @@ mod tests { ) } - #[test] - fn test_update_device_chats() { + #[async_std::test] + async fn test_update_device_chats() { let t = dummy_context(); t.ctx.update_device_chats().ok(); let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap(); assert_eq!(chats.len(), 2); - chats.get_chat_id(0).delete(&t.ctx).ok(); - chats.get_chat_id(1).delete(&t.ctx).ok(); + chats.get_chat_id(0).delete(&t.ctx).await.ok(); + chats.get_chat_id(1).delete(&t.ctx).await.ok(); let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap(); assert_eq!(chats.len(), 0); diff --git a/src/test_utils.rs b/src/test_utils.rs index 4b0e30b7c..2c134cc28 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -77,9 +77,10 @@ pub(crate) fn alice_keypair() -> key::KeyPair { /// Creates Alice with a pre-generated keypair. /// /// Returns the address of the keypair created (alice@example.com). -pub(crate) fn configure_alice_keypair(ctx: &Context) -> String { +pub(crate) async fn configure_alice_keypair(ctx: &Context) -> String { let keypair = alice_keypair(); ctx.set_config(Config::ConfiguredAddr, Some(&keypair.addr.to_string())) + .await .unwrap(); key::store_self_keypair(&ctx, &keypair, key::KeyPairUse::Default) .expect("Failed to save Alice's key"); From 618202cf8b935f6ac99c106c2fd3f88aaacfbfa4 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 4 Mar 2020 16:48:14 +0100 Subject: [PATCH 002/118] more async --- Cargo.lock | 2 + src/configure/mod.rs | 90 +++------ src/context.rs | 52 ++--- src/imap/idle.rs | 7 +- src/imap/mod.rs | 437 +++++++++++++++++++++---------------------- src/job.rs | 146 +++++---------- src/job_thread.rs | 75 ++++---- src/smtp/mod.rs | 44 +++-- src/smtp/send.rs | 11 +- 9 files changed, 373 insertions(+), 491 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 80c6478ae..9ca9f0566 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -141,6 +141,7 @@ name = "async-std" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ + "async-attributes 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "async-task 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "broadcaster 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -643,6 +644,7 @@ dependencies = [ "escaper 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "hex 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "image 0.22.5 (registry+https://github.com/rust-lang/crates.io-index)", "image-meta 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/src/configure/mod.rs b/src/configure/mod.rs index 3aaa1efa1..111f943d6 100644 --- a/src/configure/mod.rs +++ b/src/configure/mod.rs @@ -6,8 +6,6 @@ mod read_url; use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; -use async_std::task; - use crate::config::Config; use crate::constants::*; use crate::context::Context; @@ -69,28 +67,11 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status { let mut param_autoconfig: Option = None; - context - .inbox_thread - .read() - .unwrap() - .imap - .disconnect(context) - .await; - context - .sentbox_thread - .read() - .unwrap() - .imap - .disconnect(context) - .await; - context - .mvbox_thread - .read() - .unwrap() - .imap - .disconnect(context) - .await; - context.smtp.clone().lock().unwrap().disconnect(); + context.inbox_thread.imap.disconnect(context).await; + context.sentbox_thread.imap.disconnect(context).await; + context.mvbox_thread.imap.disconnect(context).await; + context.smtp.disconnect().await; + info!(context, "Configure ...",); // Variables that are shared between steps: @@ -360,21 +341,21 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status { /* try to connect to IMAP - if we did not got an autoconfig, do some further tries with different settings and username variations */ imap_connected_here = - try_imap_connections(context, &mut param, param_autoconfig.is_some()); + try_imap_connections(context, &mut param, param_autoconfig.is_some()).await; imap_connected_here } 15 => { progress!(context, 800); smtp_connected_here = - try_smtp_connections(context, &mut param, param_autoconfig.is_some()); + try_smtp_connections(context, &mut param, param_autoconfig.is_some()).await; smtp_connected_here } 16 => { progress!(context, 900); let create_mvbox = context.get_config_bool(Config::MvboxWatch) || context.get_config_bool(Config::MvboxMove); - let imap = &context.inbox_thread.read().unwrap().imap; - if let Err(err) = imap.ensure_configured_folders(context, create_mvbox) { + let imap = &context.inbox_thread.imap; + if let Err(err) = imap.ensure_configured_folders(context, create_mvbox).await { warn!(context, "configuring folders failed: {:?}", err); false } else { @@ -424,16 +405,10 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status { } } if imap_connected_here { - context - .inbox_thread - .read() - .unwrap() - .imap - .disconnect(context) - .await; + context.inbox_thread.imap.disconnect(context).await; } if smtp_connected_here { - context.smtp.clone().lock().unwrap().disconnect(); + context.smtp.disconnect().await; } // remember the entered parameters on success @@ -522,13 +497,13 @@ fn get_offline_autoconfig(context: &Context, param: &LoginParam) -> Option bool { // progress 650 and 660 - if let Some(res) = try_imap_connection(context, &mut param, was_autoconfig, 0) { + if let Some(res) = try_imap_connection(context, &mut param, was_autoconfig, 0).await { return res; } progress!(context, 670); @@ -543,20 +518,20 @@ fn try_imap_connections( param.send_user = param.send_user.split_at(at).0.to_string(); } // progress 680 and 690 - if let Some(res) = try_imap_connection(context, &mut param, was_autoconfig, 1) { + if let Some(res) = try_imap_connection(context, &mut param, was_autoconfig, 1).await { res } else { false } } -fn try_imap_connection( +async fn try_imap_connection( context: &Context, param: &mut LoginParam, was_autoconfig: bool, variation: usize, ) -> Option { - if let Some(res) = try_imap_one_param(context, ¶m) { + if let Some(res) = try_imap_one_param(context, ¶m).await { return Some(res); } if was_autoconfig { @@ -565,17 +540,17 @@ fn try_imap_connection( progress!(context, 650 + variation * 30); param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS); param.server_flags |= DC_LP_IMAP_SOCKET_STARTTLS; - if let Some(res) = try_imap_one_param(context, ¶m) { + if let Some(res) = try_imap_one_param(context, ¶m).await { return Some(res); } progress!(context, 660 + variation * 30); param.mail_port = 143; - try_imap_one_param(context, ¶m) + try_imap_one_param(context, ¶m).await } -fn try_imap_one_param(context: &Context, param: &LoginParam) -> Option { +async fn try_imap_one_param(context: &Context, param: &LoginParam) -> Option { let inf = format!( "imap: {}@{}:{} flags=0x{:x} certificate_checks={}", param.mail_user, @@ -585,14 +560,7 @@ fn try_imap_one_param(context: &Context, param: &LoginParam) -> Option { param.imap_certificate_checks ); info!(context, "Trying: {}", inf); - if task::block_on( - context - .inbox_thread - .read() - .unwrap() - .imap - .connect(context, ¶m), - ) { + if context.inbox_thread.imap.connect(context, ¶m).await { info!(context, "success: {}", inf); return Some(true); } @@ -603,13 +571,13 @@ fn try_imap_one_param(context: &Context, param: &LoginParam) -> Option { None } -fn try_smtp_connections( +async fn try_smtp_connections( context: &Context, mut param: &mut LoginParam, was_autoconfig: bool, ) -> bool { /* try to connect to SMTP - if we did not got an autoconfig, the first try was SSL-465 and we do a second try with STARTTLS-587 */ - if let Some(res) = try_smtp_one_param(context, ¶m) { + if let Some(res) = try_smtp_one_param(context, ¶m).await { return res; } if was_autoconfig { @@ -620,32 +588,26 @@ fn try_smtp_connections( param.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32; param.send_port = 587; - if let Some(res) = try_smtp_one_param(context, ¶m) { + if let Some(res) = try_smtp_one_param(context, ¶m).await { return res; } progress!(context, 860); param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32); param.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32; param.send_port = 25; - if let Some(res) = try_smtp_one_param(context, ¶m) { + if let Some(res) = try_smtp_one_param(context, ¶m).await { return res; } false } -fn try_smtp_one_param(context: &Context, param: &LoginParam) -> Option { +async fn try_smtp_one_param(context: &Context, param: &LoginParam) -> Option { let inf = format!( "smtp: {}@{}:{} flags: 0x{:x}", param.send_user, param.send_server, param.send_port, param.server_flags ); info!(context, "Trying: {}", inf); - match context - .smtp - .clone() - .lock() - .unwrap() - .connect(context, ¶m) - { + match context.smtp.connect(context, ¶m).await { Ok(()) => { info!(context, "success: {}", inf); Some(true) diff --git a/src/context.rs b/src/context.rs index ce98962ed..8902323f4 100644 --- a/src/context.rs +++ b/src/context.rs @@ -41,10 +41,10 @@ pub struct Context { pub sql: Sql, pub perform_inbox_jobs_needed: Arc>, pub probe_imap_network: Arc>, - pub inbox_thread: Arc>, - pub sentbox_thread: Arc>, - pub mvbox_thread: Arc>, - pub smtp: Arc>, + pub inbox_thread: JobThread, + pub sentbox_thread: JobThread, + pub mvbox_thread: JobThread, + pub smtp: Smtp, pub smtp_state: Arc<(Mutex, Condvar)>, pub oauth2_critical: Arc>, #[debug_stub = "Callback"] @@ -113,27 +113,15 @@ impl Context { os_name: Some(os_name), running_state: Arc::new(RwLock::new(Default::default())), sql: Sql::new(), - smtp: Arc::new(Mutex::new(Smtp::new())), + smtp: Smtp::new(), smtp_state: Arc::new((Mutex::new(Default::default()), Condvar::new())), oauth2_critical: Arc::new(Mutex::new(())), bob: Arc::new(RwLock::new(Default::default())), last_smeared_timestamp: RwLock::new(0), cmdline_sel_chat_id: Arc::new(RwLock::new(ChatId::new(0))), - inbox_thread: Arc::new(RwLock::new(JobThread::new( - "INBOX", - "configured_inbox_folder", - Imap::new(), - ))), - sentbox_thread: Arc::new(RwLock::new(JobThread::new( - "SENTBOX", - "configured_sentbox_folder", - Imap::new(), - ))), - mvbox_thread: Arc::new(RwLock::new(JobThread::new( - "MVBOX", - "configured_mvbox_folder", - Imap::new(), - ))), + inbox_thread: JobThread::new("INBOX", "configured_inbox_folder", Imap::new()), + sentbox_thread: JobThread::new("SENTBOX", "configured_sentbox_folder", Imap::new()), + mvbox_thread: JobThread::new("MVBOX", "configured_mvbox_folder", Imap::new()), probe_imap_network: Arc::new(RwLock::new(false)), perform_inbox_jobs_needed: Arc::new(RwLock::new(false)), generating_key_mutex: Mutex::new(()), @@ -459,28 +447,14 @@ impl Drop for Context { fn drop(&mut self) { async_std::task::block_on(async move { info!(self, "disconnecting inbox-thread"); - self.inbox_thread - .read() - .unwrap() - .imap - .disconnect(self) - .await; + self.inbox_thread.imap.disconnect(self).await; info!(self, "disconnecting sentbox-thread"); - self.sentbox_thread - .read() - .unwrap() - .imap - .disconnect(self) - .await; + self.sentbox_thread.imap.disconnect(self).await; info!(self, "disconnecting mvbox-thread"); - self.mvbox_thread - .read() - .unwrap() - .imap - .disconnect(self) - .await; + self.mvbox_thread.imap.disconnect(self).await; info!(self, "disconnecting SMTP"); - self.smtp.clone().lock().unwrap().disconnect(); + self.smtp.disconnect().await; + self.sql.close(self); }); } diff --git a/src/imap/idle.rs b/src/imap/idle.rs index 537f0fb40..7c07bbaf9 100644 --- a/src/imap/idle.rs +++ b/src/imap/idle.rs @@ -4,7 +4,6 @@ use async_imap::extensions::idle::{Handle as ImapIdleHandle, IdleResponse}; use async_native_tls::TlsStream; use async_std::net::TcpStream; use async_std::prelude::*; -use async_std::task; use std::sync::atomic::Ordering; use std::time::{Duration, SystemTime}; @@ -61,12 +60,12 @@ impl Session { } impl Imap { - pub fn can_idle(&self) -> bool { - task::block_on(async move { self.config.read().await.can_idle }) + pub async fn can_idle(&self) -> bool { + self.config.read().await.can_idle } pub async fn idle(&self, context: &Context, watch_folder: Option) -> Result<()> { - if !self.can_idle() { + if !self.can_idle().await { return Err(Error::IdleAbilityMissing); } diff --git a/src/imap/mod.rs b/src/imap/mod.rs index ea62f64e2..32a365051 100644 --- a/src/imap/mod.rs +++ b/src/imap/mod.rs @@ -12,7 +12,6 @@ use async_imap::{ types::{Capability, Fetch, Flag, Mailbox, Name, NameAttribute}, }; use async_std::sync::{Mutex, RwLock}; -use async_std::task; use crate::config::*; use crate::constants::*; @@ -375,7 +374,7 @@ impl Imap { // the trailing underscore is correct if self.connect(context, ¶m).await { - self.ensure_configured_folders(context, true) + self.ensure_configured_folders(context, true).await } else { Err(Error::ConnectionFailed(format!("{}", param))) } @@ -964,112 +963,112 @@ impl Imap { } } - pub fn set_seen(&self, context: &Context, folder: &str, uid: u32) -> ImapActionResult { - task::block_on(async move { - if let Some(imapresult) = self - .prepare_imap_operation_on_msg(context, folder, uid) - .await - { - return imapresult; - } - // we are connected, and the folder is selected - info!(context, "Marking message {}/{} as seen...", folder, uid,); + pub async fn set_seen(&self, context: &Context, folder: &str, uid: u32) -> ImapActionResult { + if let Some(imapresult) = self + .prepare_imap_operation_on_msg(context, folder, uid) + .await + { + return imapresult; + } + // 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 { - warn!( - context, - "Cannot mark message {} in folder {} as seen, ignoring.", uid, folder - ); - ImapActionResult::Failed - } - }) + if self.add_flag_finalized(context, uid, "\\Seen").await { + ImapActionResult::Success + } else { + warn!( + context, + "Cannot mark message {} in folder {} as seen, ignoring.", uid, folder + ); + ImapActionResult::Failed + } } - pub fn delete_msg( + pub async fn delete_msg( &self, context: &Context, message_id: &str, folder: &str, uid: &mut u32, ) -> ImapActionResult { - task::block_on(async move { - if let Some(imapresult) = self - .prepare_imap_operation_on_msg(context, folder, *uid) - .await - { - return imapresult; - } - // we are connected, and the folder is selected + if let Some(imapresult) = self + .prepare_imap_operation_on_msg(context, folder, *uid) + .await + { + return imapresult; + } + // we are connected, and the folder is selected - let set = format!("{}", uid); - let display_imap_id = format!("{}/{}", folder, uid); + let set = format!("{}", uid); + let display_imap_id = format!("{}/{}", folder, uid); - // double-check that we are deleting the correct message-id - // this comes at the expense of another imap query - if let Some(ref mut session) = &mut *self.session.lock().await { - match session.uid_fetch(set, DELETE_CHECK_FLAGS).await { - Ok(msgs) => { - let fetch = if let Some(fetch) = msgs.first() { - fetch - } else { - warn!( - context, - "Cannot delete on IMAP, {}: imap entry gone '{}'", - display_imap_id, - message_id, - ); - return ImapActionResult::Failed; - }; - - let remote_message_id = get_fetch_headers(fetch) - .and_then(|headers| prefetch_get_message_id(&headers)) - .unwrap_or_default(); - - if remote_message_id != message_id { - warn!( - context, - "Cannot delete on IMAP, {}: remote message-id '{}' != '{}'", - display_imap_id, - remote_message_id, - message_id, - ); - *uid = 0; - } - } - Err(err) => { + // double-check that we are deleting the correct message-id + // this comes at the expense of another imap query + if let Some(ref mut session) = &mut *self.session.lock().await { + match session.uid_fetch(set, DELETE_CHECK_FLAGS).await { + Ok(msgs) => { + let fetch = if let Some(fetch) = msgs.first() { + fetch + } else { warn!( context, - "Cannot delete {} on IMAP: {}", display_imap_id, err + "Cannot delete on IMAP, {}: imap entry gone '{}'", + display_imap_id, + message_id, + ); + return ImapActionResult::Failed; + }; + + let remote_message_id = get_fetch_headers(fetch) + .and_then(|headers| prefetch_get_message_id(&headers)) + .unwrap_or_default(); + + if remote_message_id != message_id { + warn!( + context, + "Cannot delete on IMAP, {}: remote message-id '{}' != '{}'", + display_imap_id, + remote_message_id, + message_id, ); *uid = 0; } } + Err(err) => { + warn!( + context, + "Cannot delete {} on IMAP: {}", display_imap_id, err + ); + *uid = 0; + } } + } - // mark the message for deletion - if !self.add_flag_finalized(context, *uid, "\\Deleted").await { - warn!( - context, - "Cannot mark message {} as \"Deleted\".", display_imap_id - ); - ImapActionResult::Failed - } else { - emit_event!( - context, - Event::ImapMessageDeleted(format!( - "IMAP Message {} marked as deleted [{}]", - display_imap_id, message_id - )) - ); - self.config.write().await.selected_folder_needs_expunge = true; - ImapActionResult::Success - } - }) + // mark the message for deletion + if !self.add_flag_finalized(context, *uid, "\\Deleted").await { + warn!( + context, + "Cannot mark message {} as \"Deleted\".", display_imap_id + ); + ImapActionResult::Failed + } else { + emit_event!( + context, + Event::ImapMessageDeleted(format!( + "IMAP Message {} marked as deleted [{}]", + display_imap_id, message_id + )) + ); + self.config.write().await.selected_folder_needs_expunge = true; + ImapActionResult::Success + } } - pub fn ensure_configured_folders(&self, context: &Context, create_mvbox: bool) -> Result<()> { + pub async fn ensure_configured_folders( + &self, + context: &Context, + create_mvbox: bool, + ) -> Result<()> { let folders_configured = context .sql .get_raw_config_int(context, "folders_configured"); @@ -1079,101 +1078,98 @@ impl Imap { return Ok(()); } - task::block_on(async move { - if !self.is_connected().await { - return Err(Error::NoConnection); - } + if !self.is_connected().await { + return Err(Error::NoConnection); + } - info!(context, "Configuring IMAP-folders."); + info!(context, "Configuring IMAP-folders."); - if let Some(ref mut session) = &mut *self.session.lock().await { - let folders = match self.list_folders(session, context).await { - Some(f) => f, - None => { - return Err(Error::Other("list_folders failed".to_string())); + if let Some(ref mut session) = &mut *self.session.lock().await { + let folders = match self.list_folders(session, context).await { + Some(f) => f, + None => { + return Err(Error::Other("list_folders failed".to_string())); + } + }; + + let sentbox_folder = folders + .iter() + .find(|folder| match get_folder_meaning(folder) { + FolderMeaning::SentObjects => true, + _ => false, + }); + info!(context, "sentbox folder is {:?}", sentbox_folder); + + let delimiter = self.config.read().await.imap_delimiter; + let fallback_folder = format!("INBOX{}DeltaChat", delimiter); + + let mut mvbox_folder = folders + .iter() + .find(|folder| folder.name() == "DeltaChat" || folder.name() == fallback_folder) + .map(|n| n.name().to_string()); + + if mvbox_folder.is_none() && create_mvbox { + info!(context, "Creating MVBOX-folder \"DeltaChat\"...",); + + match session.create("DeltaChat").await { + Ok(_) => { + mvbox_folder = Some("DeltaChat".into()); + + info!(context, "MVBOX-folder created.",); } - }; + Err(err) => { + warn!( + context, + "Cannot create MVBOX-folder, trying to create INBOX subfolder. ({})", + err + ); - let sentbox_folder = - folders - .iter() - .find(|folder| match get_folder_meaning(folder) { - FolderMeaning::SentObjects => true, - _ => false, - }); - info!(context, "sentbox folder is {:?}", sentbox_folder); - - let delimiter = self.config.read().await.imap_delimiter; - let fallback_folder = format!("INBOX{}DeltaChat", delimiter); - - let mut mvbox_folder = folders - .iter() - .find(|folder| folder.name() == "DeltaChat" || folder.name() == fallback_folder) - .map(|n| n.name().to_string()); - - if mvbox_folder.is_none() && create_mvbox { - info!(context, "Creating MVBOX-folder \"DeltaChat\"...",); - - match session.create("DeltaChat").await { - Ok(_) => { - mvbox_folder = Some("DeltaChat".into()); - - info!(context, "MVBOX-folder created.",); - } - Err(err) => { - warn!( - context, - "Cannot create MVBOX-folder, trying to create INBOX subfolder. ({})", - err - ); - - match session.create(&fallback_folder).await { - Ok(_) => { - mvbox_folder = Some(fallback_folder); - info!( - context, - "MVBOX-folder created as INBOX subfolder. ({})", err - ); - } - Err(err) => { - warn!(context, "Cannot create MVBOX-folder. ({})", err); - } + match session.create(&fallback_folder).await { + Ok(_) => { + mvbox_folder = Some(fallback_folder); + info!( + context, + "MVBOX-folder created as INBOX subfolder. ({})", err + ); + } + Err(err) => { + warn!(context, "Cannot create MVBOX-folder. ({})", err); } } } - // SUBSCRIBE is needed to make the folder visible to the LSUB command - // that may be used by other MUAs to list folders. - // for the LIST command, the folder is always visible. - if let Some(ref mvbox) = mvbox_folder { - if let Err(err) = session.subscribe(mvbox).await { - warn!(context, "could not subscribe to {:?}: {:?}", mvbox, err); - } + } + // SUBSCRIBE is needed to make the folder visible to the LSUB command + // that may be used by other MUAs to list folders. + // for the LIST command, the folder is always visible. + if let Some(ref mvbox) = mvbox_folder { + if let Err(err) = session.subscribe(mvbox).await { + warn!(context, "could not subscribe to {:?}: {:?}", mvbox, err); } } - context - .sql - .set_raw_config(context, "configured_inbox_folder", Some("INBOX"))?; - if let Some(ref mvbox_folder) = mvbox_folder { - context.sql.set_raw_config( - context, - "configured_mvbox_folder", - Some(mvbox_folder), - )?; - } - if let Some(ref sentbox_folder) = sentbox_folder { - context.sql.set_raw_config( - context, - "configured_sentbox_folder", - Some(sentbox_folder.name()), - )?; - } - context - .sql - .set_raw_config_int(context, "folders_configured", 3)?; } - info!(context, "FINISHED configuring IMAP-folders."); - Ok(()) - }) + context + .sql + .set_raw_config(context, "configured_inbox_folder", Some("INBOX"))?; + if let Some(ref mvbox_folder) = mvbox_folder { + context.sql.set_raw_config( + context, + "configured_mvbox_folder", + Some(mvbox_folder), + )?; + } + if let Some(ref sentbox_folder) = sentbox_folder { + context.sql.set_raw_config( + context, + "configured_sentbox_folder", + Some(sentbox_folder.name()), + )?; + } + context + .sql + .set_raw_config_int(context, "folders_configured", 3)?; + } + info!(context, "FINISHED configuring IMAP-folders."); + Ok(()) } async fn list_folders(&self, session: &mut Session, context: &Context) -> Option> { @@ -1193,59 +1189,56 @@ impl Imap { } } - pub fn empty_folder(&self, context: &Context, folder: &str) { - task::block_on(async move { - info!(context, "emptying folder {}", folder); + pub async fn empty_folder(&self, context: &Context, folder: &str) { + info!(context, "emptying folder {}", folder); - // we want to report all error to the user - // (no retry should be attempted) - if folder.is_empty() { - error!(context, "cannot perform empty, folder not set"); - return; - } - if let Err(err) = self.setup_handle_if_needed(context).await { - error!(context, "could not setup imap connection: {:?}", err); - return; - } - if let Err(err) = self.select_folder(context, Some(&folder)).await { - error!( - context, - "Could not select {} for expunging: {:?}", folder, err - ); - return; - } - - if !self - .add_flag_finalized_with_set(context, SELECT_ALL, "\\Deleted") - .await - { - error!(context, "Cannot mark messages for deletion {}", folder); - return; - } - - // we now trigger expunge to actually delete messages - self.config.write().await.selected_folder_needs_expunge = true; - match self.select_folder::(context, None).await { - Ok(()) => { - emit_event!(context, Event::ImapFolderEmptied(folder.to_string())); - } - Err(err) => { - error!(context, "expunge failed {}: {:?}", folder, err); - } - } - - if let Err(err) = crate::sql::execute( + // we want to report all error to the user + // (no retry should be attempted) + if folder.is_empty() { + error!(context, "cannot perform empty, folder not set"); + return; + } + if let Err(err) = self.setup_handle_if_needed(context).await { + error!(context, "could not setup imap connection: {:?}", err); + return; + } + if let Err(err) = self.select_folder(context, Some(&folder)).await { + error!( context, - &context.sql, - "UPDATE msgs SET server_folder='',server_uid=0 WHERE server_folder=?", - params![folder], - ) { - warn!( - context, - "Failed to reset server_uid and server_folder for deleted messages: {}", err - ); + "Could not select {} for expunging: {:?}", folder, err + ); + return; + } + + if !self + .add_flag_finalized_with_set(context, SELECT_ALL, "\\Deleted") + .await + { + error!(context, "Cannot mark messages for deletion {}", folder); + return; + } + + // we now trigger expunge to actually delete messages + self.config.write().await.selected_folder_needs_expunge = true; + match self.select_folder::(context, None).await { + Ok(()) => { + emit_event!(context, Event::ImapFolderEmptied(folder.to_string())); } - }); + Err(err) => { + error!(context, "expunge failed {}: {:?}", folder, err); + } + } + if let Err(err) = crate::sql::execute( + context, + &context.sql, + "UPDATE msgs SET server_folder='',server_uid=0 WHERE server_folder=?", + params![folder], + ) { + warn!( + context, + "Failed to reset server_uid and server_folder for deleted messages: {}", err + ); + } } } diff --git a/src/job.rs b/src/job.rs index bbda38062..7c201cd70 100644 --- a/src/job.rs +++ b/src/job.rs @@ -185,12 +185,15 @@ impl Job { // its ok/error response processing. Note that if a message // was sent we need to mark it in the database ASAP as we // otherwise might send it twice. - let mut smtp = context.smtp.lock().unwrap(); if std::env::var(crate::DCC_MIME_DEBUG).is_ok() { info!(context, "smtp-sending out mime message:"); println!("{}", String::from_utf8_lossy(&message)); } - match smtp.send(context, recipients, message, job_id).await { + match context + .smtp + .send(context, recipients, message, job_id) + .await + { Err(crate::smtp::send::Error::SendError(err)) => { // Remote error, retry later. warn!(context, "SMTP failed to send: {}", err); @@ -206,7 +209,7 @@ impl Job { Status::RetryLater } _ => { - if smtp.has_maybe_stale_connection() { + if context.smtp.has_maybe_stale_connection().await { info!(context, "stale connection? immediately reconnecting"); Status::RetryNow } else { @@ -216,13 +219,13 @@ impl Job { }; // this clears last_success info - smtp.disconnect(); + context.smtp.disconnect().await; res } Err(crate::smtp::send::Error::EnvelopeError(err)) => { // Local error, job is invalid, do not retry. - smtp.disconnect(); + context.smtp.disconnect().await; warn!(context, "SMTP job is invalid: {}", err); Status::Finished(Err(Error::SmtpError(err))) } @@ -241,9 +244,9 @@ impl Job { async fn send_msg_to_smtp(&mut self, context: &Context) -> Status { // connect to SMTP server, if not yet done - if !context.smtp.lock().unwrap().is_connected() { + if !context.smtp.is_connected().await { let loginparam = LoginParam::from_database(context, "configured_"); - if let Err(err) = context.smtp.lock().unwrap().connect(context, &loginparam) { + if let Err(err) = context.smtp.connect(context, &loginparam).await { warn!(context, "SMTP connection failure: {:?}", err); return Status::RetryLater; } @@ -384,10 +387,10 @@ impl Job { .map_err(|err| format_err!("invalid recipient: {} {:?}", addr, err))); let recipients = vec![recipient]; - /* connect to SMTP server, if not yet done */ - if !context.smtp.lock().unwrap().is_connected() { + // connect to SMTP server, if not yet done + if !context.smtp.is_connected().await { let loginparam = LoginParam::from_database(context, "configured_"); - if let Err(err) = context.smtp.lock().unwrap().connect(context, &loginparam) { + if let Err(err) = context.smtp.connect(context, &loginparam).await { warn!(context, "SMTP connection failure: {:?}", err); return Status::RetryLater; } @@ -404,11 +407,11 @@ impl Job { } async fn move_msg(&mut self, context: &Context) -> Status { - let imap_inbox = &context.inbox_thread.read().unwrap().imap; + let imap_inbox = &context.inbox_thread.imap; let msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id))); - if let Err(err) = imap_inbox.ensure_configured_folders(context, true) { + if let Err(err) = imap_inbox.ensure_configured_folders(context, true).await { warn!(context, "could not configure folders: {:?}", err); return Status::RetryLater; } @@ -446,7 +449,7 @@ impl Job { } async fn delete_msg_on_imap(&mut self, context: &Context) -> Status { - let imap_inbox = &context.inbox_thread.read().unwrap().imap; + let imap_inbox = &context.inbox_thread.imap; let mut msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id))); @@ -461,7 +464,9 @@ impl Job { we delete the message from the server */ let mid = msg.rfc724_mid; let server_folder = msg.server_folder.as_ref().unwrap(); - let res = imap_inbox.delete_msg(context, &mid, server_folder, &mut msg.server_uid); + let res = imap_inbox + .delete_msg(context, &mid, server_folder, &mut msg.server_uid) + .await; if res == ImapActionResult::RetryLater { // XXX RetryLater is converted to RetryNow here return Status::RetryNow; @@ -476,28 +481,28 @@ impl Job { } async fn empty_server(&mut self, context: &Context) -> Status { - let imap_inbox = &context.inbox_thread.read().unwrap().imap; + let imap_inbox = &context.inbox_thread.imap; if self.foreign_id & DC_EMPTY_MVBOX > 0 { if let Some(mvbox_folder) = context .sql .get_raw_config(context, "configured_mvbox_folder") { - imap_inbox.empty_folder(context, &mvbox_folder); + imap_inbox.empty_folder(context, &mvbox_folder).await; } } if self.foreign_id & DC_EMPTY_INBOX > 0 { - imap_inbox.empty_folder(context, "INBOX"); + imap_inbox.empty_folder(context, "INBOX").await; } Status::Finished(Ok(())) } async fn markseen_msg_on_imap(&mut self, context: &Context) -> Status { - let imap_inbox = &context.inbox_thread.read().unwrap().imap; + let imap_inbox = &context.inbox_thread.imap; let msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id))); let folder = msg.server_folder.as_ref().unwrap(); - match imap_inbox.set_seen(context, folder, msg.server_uid) { + match imap_inbox.set_seen(context, folder, msg.server_uid).await { ImapActionResult::RetryLater => Status::RetryLater, ImapActionResult::AlreadyDone => Status::Finished(Ok(())), ImapActionResult::Success | ImapActionResult::Failed => { @@ -525,12 +530,12 @@ impl Job { .unwrap_or_default() .to_string(); let uid = self.param.get_int(Param::ServerUid).unwrap_or_default() as u32; - let imap_inbox = &context.inbox_thread.read().unwrap().imap; - if imap_inbox.set_seen(context, &folder, uid) == ImapActionResult::RetryLater { + let imap_inbox = &context.inbox_thread.imap; + if imap_inbox.set_seen(context, &folder, uid).await == ImapActionResult::RetryLater { return Status::RetryLater; } if self.param.get_bool(Param::AlsoMove).unwrap_or_default() { - if let Err(err) = imap_inbox.ensure_configured_folders(context, true) { + if let Err(err) = imap_inbox.ensure_configured_folders(context, true).await { warn!(context, "configuring folders failed: {:?}", err); return Status::RetryLater; } @@ -584,34 +589,19 @@ pub async fn kill_ids(context: &Context, job_ids: &[u32]) -> sql::Result<()> { pub async fn perform_inbox_fetch(context: &Context) { let use_network = context.get_config_bool(Config::InboxWatch); - context - .inbox_thread - .write() - .unwrap() - .fetch(context, use_network) - .await; + context.inbox_thread.fetch(context, use_network).await; } pub async fn perform_mvbox_fetch(context: &Context) { let use_network = context.get_config_bool(Config::MvboxWatch); - context - .mvbox_thread - .write() - .unwrap() - .fetch(context, use_network) - .await; + context.mvbox_thread.fetch(context, use_network).await; } pub async fn perform_sentbox_fetch(context: &Context) { let use_network = context.get_config_bool(Config::SentboxWatch); - context - .sentbox_thread - .write() - .unwrap() - .fetch(context, use_network) - .await; + context.sentbox_thread.fetch(context, use_network).await; } pub async fn perform_inbox_idle(context: &Context) { @@ -624,34 +614,19 @@ pub async fn perform_inbox_idle(context: &Context) { } let use_network = context.get_config_bool(Config::InboxWatch); - context - .inbox_thread - .read() - .unwrap() - .idle(context, use_network) - .await; + context.inbox_thread.idle(context, use_network).await; } pub async fn perform_mvbox_idle(context: &Context) { let use_network = context.get_config_bool(Config::MvboxWatch); - context - .mvbox_thread - .read() - .unwrap() - .idle(context, use_network) - .await; + context.mvbox_thread.idle(context, use_network).await; } pub async fn perform_sentbox_idle(context: &Context) { let use_network = context.get_config_bool(Config::SentboxWatch); - context - .sentbox_thread - .read() - .unwrap() - .idle(context, use_network) - .await; + context.sentbox_thread.idle(context, use_network).await; } pub async fn interrupt_inbox_idle(context: &Context) { @@ -660,33 +635,18 @@ pub async fn interrupt_inbox_idle(context: &Context) { // because we don't know in which state the thread is. // If it's currently fetching then we can not get the lock // but we flag it for checking jobs so that idle will be skipped. - match context.inbox_thread.try_read() { - Ok(inbox_thread) => { - inbox_thread.interrupt_idle(context).await; - } - Err(err) => { - *context.perform_inbox_jobs_needed.write().unwrap() = true; - warn!(context, "could not interrupt idle: {}", err); - } + if !context.inbox_thread.try_interrupt_idle(context).await { + *context.perform_inbox_jobs_needed.write().unwrap() = true; + warn!(context, "could not interrupt idle"); } } pub async fn interrupt_mvbox_idle(context: &Context) { - context - .mvbox_thread - .read() - .unwrap() - .interrupt_idle(context) - .await; + context.mvbox_thread.interrupt_idle(context).await; } pub async fn interrupt_sentbox_idle(context: &Context) { - context - .sentbox_thread - .read() - .unwrap() - .interrupt_idle(context) - .await; + context.sentbox_thread.interrupt_idle(context).await; } pub async fn perform_smtp_jobs(context: &Context) { @@ -939,20 +899,8 @@ async fn job_perform(context: &Context, thread: Thread, probe_network: bool) { // - they can be re-executed one time AT_ONCE, but they are not saved in the database for later execution if Action::ConfigureImap == job.action || Action::ImexImap == job.action { job::kill_action(context, job.action).await; - context - .sentbox_thread - .clone() - .read() - .unwrap() - .suspend(context) - .await; - context - .mvbox_thread - .clone() - .read() - .unwrap() - .suspend(context) - .await; + context.sentbox_thread.suspend(context).await; + context.mvbox_thread.suspend(context).await; suspend_smtp_thread(context, true); } @@ -962,18 +910,8 @@ async fn job_perform(context: &Context, thread: Thread, probe_network: bool) { }; if Action::ConfigureImap == job.action || Action::ImexImap == job.action { - context - .sentbox_thread - .clone() - .read() - .unwrap() - .unsuspend(context); - context - .mvbox_thread - .clone() - .read() - .unwrap() - .unsuspend(context); + context.sentbox_thread.unsuspend(context).await; + context.mvbox_thread.unsuspend(context).await; suspend_smtp_thread(context, false); break; } diff --git a/src/job_thread.rs b/src/job_thread.rs index c36beb757..3676eb42e 100644 --- a/src/job_thread.rs +++ b/src/job_thread.rs @@ -1,4 +1,4 @@ -use std::sync::{Arc, Condvar, Mutex}; +use async_std::sync::{channel, Arc, Mutex, Receiver, Sender}; use crate::context::Context; use crate::error::{Error, Result}; @@ -9,12 +9,13 @@ pub struct JobThread { pub name: &'static str, pub folder_config_name: &'static str, pub imap: Imap, - pub state: Arc<(Mutex, Condvar)>, + pub state: Arc>, + notify_sender: Sender<()>, + notify_receiver: Receiver<()>, } #[derive(Clone, Debug, Default)] pub struct JobState { - idle: bool, jobs_needed: bool, suspended: bool, using_handle: bool, @@ -22,22 +23,26 @@ pub struct JobState { impl JobThread { pub fn new(name: &'static str, folder_config_name: &'static str, imap: Imap) -> Self { + let (notify_sender, notify_receiver) = channel(1); + JobThread { name, folder_config_name, imap, - state: Arc::new((Mutex::new(Default::default()), Condvar::new())), + state: Arc::new(Mutex::new(Default::default())), + notify_sender, + notify_receiver, } } pub async fn suspend(&self, context: &Context) { info!(context, "Suspending {}-thread.", self.name,); { - self.state.0.lock().unwrap().suspended = true; + self.state.lock().await.suspended = true; } self.interrupt_idle(context).await; loop { - let using_handle = self.state.0.lock().unwrap().using_handle; + let using_handle = self.state.lock().await.using_handle; if !using_handle { return; } @@ -45,38 +50,45 @@ impl JobThread { } } - pub fn unsuspend(&self, context: &Context) { + pub async fn unsuspend(&self, context: &Context) { info!(context, "Unsuspending {}-thread.", self.name); - let &(ref lock, ref cvar) = &*self.state.clone(); - let mut state = lock.lock().unwrap(); + { + let lock = &*self.state.clone(); + let mut state = lock.lock().await; - state.suspended = false; - state.idle = true; - cvar.notify_one(); + state.suspended = false; + } + self.notify_sender.send(()).await; + } + + pub async fn try_interrupt_idle(&self, context: &Context) -> bool { + if self.state.lock().await.using_handle { + self.interrupt_idle(context).await; + return true; + } + + false } pub async fn interrupt_idle(&self, context: &Context) { { - self.state.0.lock().unwrap().jobs_needed = true; + self.state.lock().await.jobs_needed = true; } info!(context, "Interrupting {}-IDLE...", self.name); self.imap.interrupt_idle(context).await; - let &(ref lock, ref cvar) = &*self.state.clone(); - let mut state = lock.lock().unwrap(); + self.notify_sender.send(()).await; - state.idle = true; - cvar.notify_one(); info!(context, "Interrupting {}-IDLE... finished", self.name); } - pub async fn fetch(&mut self, context: &Context, use_network: bool) { + pub async fn fetch(&self, context: &Context, use_network: bool) { { - let &(ref lock, _) = &*self.state.clone(); - let mut state = lock.lock().unwrap(); + let lock = &*self.state.clone(); + let mut state = lock.lock().await; if state.suspended { return; @@ -94,10 +106,10 @@ impl JobThread { } } } - self.state.0.lock().unwrap().using_handle = false; + self.state.lock().await.using_handle = false; } - async fn connect_and_fetch(&mut self, context: &Context) -> Result<()> { + async fn connect_and_fetch(&self, context: &Context) -> Result<()> { let prefix = format!("{}-fetch", self.name); match self.imap.connect_configured(context).await { Ok(()) => { @@ -137,8 +149,8 @@ impl JobThread { pub async fn idle(&self, context: &Context, use_network: bool) { { - let &(ref lock, ref cvar) = &*self.state.clone(); - let mut state = lock.lock().unwrap(); + let lock = &*self.state.clone(); + let mut state = lock.lock().await; if state.jobs_needed { info!( @@ -151,10 +163,7 @@ impl JobThread { } if state.suspended { - while !state.idle { - state = cvar.wait(state).unwrap(); - } - state.idle = false; + self.notify_receiver.recv().await; return; } @@ -162,11 +171,7 @@ impl JobThread { if !use_network { state.using_handle = false; - - while !state.idle { - state = cvar.wait(state).unwrap(); - } - state.idle = false; + self.notify_receiver.recv().await; return; } } @@ -174,7 +179,7 @@ impl JobThread { let prefix = format!("{}-IDLE", self.name); let do_fake_idle = match self.imap.connect_configured(context).await { Ok(()) => { - if !self.imap.can_idle() { + if !self.imap.can_idle().await { true // we have to do fake_idle } else { let watch_folder = self.get_watch_folder(context); @@ -202,6 +207,6 @@ impl JobThread { self.imap.fake_idle(context, watch_folder).await; } - self.state.0.lock().unwrap().using_handle = false; + self.state.lock().await.using_handle = false; } } diff --git a/src/smtp/mod.rs b/src/smtp/mod.rs index 39b999171..6359a0978 100644 --- a/src/smtp/mod.rs +++ b/src/smtp/mod.rs @@ -4,6 +4,8 @@ pub mod send; use std::time::{Duration, Instant}; +use async_std::sync::RwLock; + use async_smtp::smtp::client::net::*; use async_smtp::*; @@ -49,8 +51,11 @@ impl From for Error { pub type Result = std::result::Result; +#[derive(Default, Debug)] +pub struct Smtp(RwLock); + #[derive(Default, DebugStub)] -pub struct Smtp { +struct SmtpInner { #[debug_stub(some = "SmtpTransport")] transport: Option, @@ -70,17 +75,18 @@ impl Smtp { } /// Disconnect the SMTP transport and drop it entirely. - pub fn disconnect(&mut self) { - if let Some(mut transport) = self.transport.take() { - async_std::task::block_on(transport.close()).ok(); + pub async fn disconnect(&self) { + let inner = &mut *self.0.write().await; + if let Some(mut transport) = inner.transport.take() { + transport.close().await.ok(); } - self.last_success = None; + inner.last_success = None; } /// Return true if smtp was connected but is not known to /// have been successfully used the last 60 seconds - pub fn has_maybe_stale_connection(&self) -> bool { - if let Some(last_success) = self.last_success { + pub async fn has_maybe_stale_connection(&self) -> bool { + if let Some(last_success) = self.0.read().await.last_success { Instant::now().duration_since(last_success).as_secs() > 60 } else { false @@ -88,20 +94,19 @@ impl Smtp { } /// Check whether we are connected. - pub fn is_connected(&self) -> bool { - self.transport + pub async fn is_connected(&self) -> bool { + self.0 + .read() + .await + .transport .as_ref() .map(|t| t.is_connected()) .unwrap_or_default() } /// Connect using the provided login params. - pub fn connect(&mut self, context: &Context, lp: &LoginParam) -> Result<()> { - async_std::task::block_on(self.inner_connect(context, lp)) - } - - async fn inner_connect(&mut self, context: &Context, lp: &LoginParam) -> Result<()> { - if self.is_connected() { + pub async fn connect(&self, context: &Context, lp: &LoginParam) -> Result<()> { + if self.is_connected().await { warn!(context, "SMTP already connected."); return Ok(()); } @@ -116,7 +121,9 @@ impl Smtp { address: lp.addr.clone(), error: err, })?; - self.from = Some(from); + + let inner = &mut *self.0.write().await; + inner.from = Some(from); let domain = &lp.send_server; let port = lp.send_port as u16; @@ -177,8 +184,9 @@ impl Smtp { let mut trans = client.into_transport(); trans.connect().await.map_err(Error::ConnectionFailure)?; - self.transport = Some(trans); - self.last_success = Some(Instant::now()); + inner.transport = Some(trans); + inner.last_success = Some(Instant::now()); + context.call_cb(Event::SmtpConnected(format!( "SMTP-LOGIN as {} ok", lp.send_user, diff --git a/src/smtp/send.rs b/src/smtp/send.rs index 306ba917d..56f77cee6 100644 --- a/src/smtp/send.rs +++ b/src/smtp/send.rs @@ -24,7 +24,7 @@ impl Smtp { /// Send a prepared mail to recipients. /// On successful send out Ok() is returned. pub async fn send( - &mut self, + &self, context: &Context, recipients: Vec, message: Vec, @@ -38,22 +38,23 @@ impl Smtp { .collect::>() .join(","); - let envelope = - Envelope::new(self.from.clone(), recipients).map_err(Error::EnvelopeError)?; + let envelope = Envelope::new(self.0.read().await.from.clone(), recipients) + .map_err(Error::EnvelopeError)?; let mail = SendableEmail::new( envelope, format!("{}", job_id), // only used for internal logging message, ); - if let Some(ref mut transport) = self.transport { + let inner = &mut *self.0.write().await; + if let Some(ref mut transport) = inner.transport { transport.send(mail).await.map_err(Error::SendError)?; context.call_cb(Event::SmtpMessageSent(format!( "Message len={} was smtp-sent to {}", message_len, recipients_display ))); - self.last_success = Some(std::time::Instant::now()); + inner.last_success = Some(std::time::Instant::now()); Ok(()) } else { From 43a8828430a64ae68cf52f147aa01a33fe0aa877 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 4 Mar 2020 17:24:42 +0100 Subject: [PATCH 003/118] asyncify smtp thread state handling --- src/context.rs | 15 ++-------- src/job.rs | 76 ++++++++++++++++-------------------------------- src/smtp/mod.rs | 40 ++++++++++++++++++++----- src/smtp/send.rs | 4 +-- 4 files changed, 61 insertions(+), 74 deletions(-) diff --git a/src/context.rs b/src/context.rs index 8902323f4..3f711fedf 100644 --- a/src/context.rs +++ b/src/context.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use std::ffi::OsString; use std::path::{Path, PathBuf}; -use std::sync::{Arc, Condvar, Mutex, RwLock}; +use std::sync::{Arc, Mutex, RwLock}; use crate::chat::*; use crate::config::Config; @@ -45,7 +45,6 @@ pub struct Context { pub sentbox_thread: JobThread, pub mvbox_thread: JobThread, pub smtp: Smtp, - pub smtp_state: Arc<(Mutex, Condvar)>, pub oauth2_critical: Arc>, #[debug_stub = "Callback"] cb: Box, @@ -114,7 +113,6 @@ impl Context { running_state: Arc::new(RwLock::new(Default::default())), sql: Sql::new(), smtp: Smtp::new(), - smtp_state: Arc::new((Mutex::new(Default::default()), Condvar::new())), oauth2_critical: Arc::new(Mutex::new(())), bob: Arc::new(RwLock::new(Default::default())), last_smeared_timestamp: RwLock::new(0), @@ -476,7 +474,7 @@ pub(crate) struct BobStatus { pub qr_scan: Option, } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone, Copy)] pub(crate) enum PerformJobsNeeded { Not, AtOnce, @@ -489,15 +487,6 @@ impl Default for PerformJobsNeeded { } } -#[derive(Default, Debug)] -pub struct SmtpState { - pub idle: bool, - pub suspended: bool, - pub doing_jobs: bool, - pub(crate) perform_jobs_needed: PerformJobsNeeded, - pub probe_network: bool, -} - pub fn get_version_str() -> &'static str { &DC_VERSION_STR } diff --git a/src/job.rs b/src/job.rs index 7c201cd70..35842d84a 100644 --- a/src/job.rs +++ b/src/job.rs @@ -6,6 +6,8 @@ use std::future::Future; use std::{fmt, time}; +use async_std::prelude::*; + use deltachat_derive::{FromSql, ToSql}; use itertools::Itertools; use rand::{thread_rng, Rng}; @@ -651,8 +653,7 @@ pub async fn interrupt_sentbox_idle(context: &Context) { pub async fn perform_smtp_jobs(context: &Context) { let probe_smtp_network = { - let &(ref lock, _) = &*context.smtp_state.clone(); - let mut state = lock.lock().unwrap(); + let state = &mut *context.smtp.state.write().await; let probe_smtp_network = state.probe_network; state.probe_network = false; @@ -670,41 +671,25 @@ pub async fn perform_smtp_jobs(context: &Context) { job_perform(context, Thread::Smtp, probe_smtp_network).await; info!(context, "SMTP-jobs ended."); - { - let &(ref lock, _) = &*context.smtp_state.clone(); - let mut state = lock.lock().unwrap(); - - state.doing_jobs = false; - } + context.smtp.state.write().await.doing_jobs = false; } pub async fn perform_smtp_idle(context: &Context) { info!(context, "SMTP-idle started...",); - { - let &(ref lock, ref cvar) = &*context.smtp_state.clone(); - let mut state = lock.lock().unwrap(); - match state.perform_jobs_needed { - PerformJobsNeeded::AtOnce => { - info!( - context, - "SMTP-idle will not be started because of waiting jobs.", - ); - } - PerformJobsNeeded::Not | PerformJobsNeeded::AvoidDos => { - let dur = get_next_wakeup_time(context, Thread::Smtp); + let perform_jobs_needed = context.smtp.state.read().await.perform_jobs_needed.clone(); - loop { - let res = cvar.wait_timeout(state, dur).unwrap(); - state = res.0; + match perform_jobs_needed { + PerformJobsNeeded::AtOnce => { + info!( + context, + "SMTP-idle will not be started because of waiting jobs.", + ); + } + PerformJobsNeeded::Not | PerformJobsNeeded::AvoidDos => { + let dur = get_next_wakeup_time(context, Thread::Smtp); - if state.idle || res.1.timed_out() { - // We received the notification and the value has been updated, we can leave. - break; - } - } - state.idle = false; - } + context.smtp.notify_receiver.recv().timeout(dur).await.ok(); } } @@ -736,10 +721,7 @@ fn get_next_wakeup_time(context: &Context, thread: Thread) -> time::Duration { pub async fn maybe_network(context: &Context) { { - let &(ref lock, _) = &*context.smtp_state.clone(); - let mut state = lock.lock().unwrap(); - state.probe_network = true; - + context.smtp.state.write().await.probe_network = true; *context.probe_imap_network.write().unwrap() = true; } @@ -901,7 +883,7 @@ async fn job_perform(context: &Context, thread: Thread, probe_network: bool) { job::kill_action(context, job.action).await; context.sentbox_thread.suspend(context).await; context.mvbox_thread.suspend(context).await; - suspend_smtp_thread(context, true); + suspend_smtp_thread(context, true).await; } let try_res = match perform_job_action(context, &mut job, thread, 0).await { @@ -912,7 +894,7 @@ async fn job_perform(context: &Context, thread: Thread, probe_network: bool) { if Action::ConfigureImap == job.action || Action::ImexImap == job.action { context.sentbox_thread.unsuspend(context).await; context.mvbox_thread.unsuspend(context).await; - suspend_smtp_thread(context, false); + suspend_smtp_thread(context, false).await; break; } @@ -938,13 +920,8 @@ async fn job_perform(context: &Context, thread: Thread, probe_network: bool) { time_offset ); if thread == Thread::Smtp && tries < JOB_RETRIES - 1 { - context - .smtp_state - .clone() - .0 - .lock() - .unwrap() - .perform_jobs_needed = PerformJobsNeeded::AvoidDos; + context.smtp.state.write().await.perform_jobs_needed = + PerformJobsNeeded::AvoidDos; } } else { info!( @@ -1043,11 +1020,11 @@ fn get_backoff_time_offset(tries: u32) -> i64 { seconds as i64 } -fn suspend_smtp_thread(context: &Context, suspend: bool) { - context.smtp_state.0.lock().unwrap().suspended = suspend; +async fn suspend_smtp_thread(context: &Context, suspend: bool) { + context.smtp.state.write().await.suspended = suspend; if suspend { loop { - if !context.smtp_state.0.lock().unwrap().doing_jobs { + if !context.smtp.state.read().await.doing_jobs { return; } std::thread::sleep(time::Duration::from_micros(300 * 1000)); @@ -1125,12 +1102,9 @@ pub async fn add( pub async fn interrupt_smtp_idle(context: &Context) { info!(context, "Interrupting SMTP-idle...",); - let &(ref lock, ref cvar) = &*context.smtp_state.clone(); - let mut state = lock.lock().unwrap(); + context.smtp.state.write().await.perform_jobs_needed = PerformJobsNeeded::AtOnce; + context.smtp.notify_sender.send(()).await; - state.perform_jobs_needed = PerformJobsNeeded::AtOnce; - state.idle = true; - cvar.notify_one(); info!(context, "Interrupting SMTP-idle... ended",); } diff --git a/src/smtp/mod.rs b/src/smtp/mod.rs index 6359a0978..bcb98a25f 100644 --- a/src/smtp/mod.rs +++ b/src/smtp/mod.rs @@ -2,15 +2,14 @@ pub mod send; +use async_std::sync::{channel, Receiver, RwLock, Sender}; use std::time::{Duration, Instant}; -use async_std::sync::RwLock; - use async_smtp::smtp::client::net::*; use async_smtp::*; use crate::constants::*; -use crate::context::Context; +use crate::context::{Context, PerformJobsNeeded}; use crate::events::Event; use crate::login_param::{dc_build_tls, LoginParam}; use crate::oauth2::*; @@ -51,8 +50,33 @@ impl From for Error { pub type Result = std::result::Result; +#[derive(Debug)] +pub struct Smtp { + inner: RwLock, + pub(crate) state: RwLock, + pub(crate) notify_sender: Sender<()>, + pub(crate) notify_receiver: Receiver<()>, +} + +impl Default for Smtp { + fn default() -> Self { + let (notify_sender, notify_receiver) = channel(1); + Smtp { + inner: Default::default(), + state: Default::default(), + notify_sender, + notify_receiver, + } + } +} + #[derive(Default, Debug)] -pub struct Smtp(RwLock); +pub struct State { + pub(crate) suspended: bool, + pub(crate) doing_jobs: bool, + pub(crate) perform_jobs_needed: PerformJobsNeeded, + pub(crate) probe_network: bool, +} #[derive(Default, DebugStub)] struct SmtpInner { @@ -76,7 +100,7 @@ impl Smtp { /// Disconnect the SMTP transport and drop it entirely. pub async fn disconnect(&self) { - let inner = &mut *self.0.write().await; + let inner = &mut *self.inner.write().await; if let Some(mut transport) = inner.transport.take() { transport.close().await.ok(); } @@ -86,7 +110,7 @@ impl Smtp { /// Return true if smtp was connected but is not known to /// have been successfully used the last 60 seconds pub async fn has_maybe_stale_connection(&self) -> bool { - if let Some(last_success) = self.0.read().await.last_success { + if let Some(last_success) = self.inner.read().await.last_success { Instant::now().duration_since(last_success).as_secs() > 60 } else { false @@ -95,7 +119,7 @@ impl Smtp { /// Check whether we are connected. pub async fn is_connected(&self) -> bool { - self.0 + self.inner .read() .await .transport @@ -122,7 +146,7 @@ impl Smtp { error: err, })?; - let inner = &mut *self.0.write().await; + let inner = &mut *self.inner.write().await; inner.from = Some(from); let domain = &lp.send_server; diff --git a/src/smtp/send.rs b/src/smtp/send.rs index 56f77cee6..6305acce3 100644 --- a/src/smtp/send.rs +++ b/src/smtp/send.rs @@ -38,7 +38,7 @@ impl Smtp { .collect::>() .join(","); - let envelope = Envelope::new(self.0.read().await.from.clone(), recipients) + let envelope = Envelope::new(self.inner.read().await.from.clone(), recipients) .map_err(Error::EnvelopeError)?; let mail = SendableEmail::new( envelope, @@ -46,7 +46,7 @@ impl Smtp { message, ); - let inner = &mut *self.0.write().await; + let inner = &mut *self.inner.write().await; if let Some(ref mut transport) = inner.transport { transport.send(mail).await.map_err(Error::SendError)?; From 62bfa5157b8c95ab6695577210714e73250dbbee Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 4 Mar 2020 17:27:25 +0100 Subject: [PATCH 004/118] async sleep --- examples/simple.rs | 4 ++-- src/imap/idle.rs | 2 +- src/job.rs | 4 ++-- src/job_thread.rs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/simple.rs b/examples/simple.rs index afcd84906..cb6ac0435 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -82,7 +82,7 @@ async fn main() { .unwrap(); ctx.configure().await; - thread::sleep(duration); + async_std::task::sleep(duration).await; println!("sending a message"); let contact_id = Contact::create(&ctx, "dignifiedquire", "dignifiedquire@gmail.com").unwrap(); @@ -101,7 +101,7 @@ async fn main() { println!("chat: {} - {:?} - {:?}", i, text1, text2,); } - thread::sleep(duration); + async_std::task::sleep(duration).await; println!("stopping threads"); diff --git a/src/imap/idle.rs b/src/imap/idle.rs index 7c07bbaf9..e8e213104 100644 --- a/src/imap/idle.rs +++ b/src/imap/idle.rs @@ -283,7 +283,7 @@ impl Imap { // not have entered idle_wait yet, give it some time // for that to happen. XXX handle this without extra wait // https://github.com/deltachat/deltachat-core-rust/issues/925 - std::thread::sleep(Duration::from_millis(200)); + async_std::task::sleep(Duration::from_millis(200)).await; info!(context, "low-level: dropping stop-source to interrupt idle"); std::mem::drop(interrupt) } diff --git a/src/job.rs b/src/job.rs index 35842d84a..f7b13557f 100644 --- a/src/job.rs +++ b/src/job.rs @@ -675,7 +675,7 @@ pub async fn perform_smtp_jobs(context: &Context) { } pub async fn perform_smtp_idle(context: &Context) { - info!(context, "SMTP-idle started...",); + info!(context, "SMTP-idle started..."); let perform_jobs_needed = context.smtp.state.read().await.perform_jobs_needed.clone(); @@ -1027,7 +1027,7 @@ async fn suspend_smtp_thread(context: &Context, suspend: bool) { if !context.smtp.state.read().await.doing_jobs { return; } - std::thread::sleep(time::Duration::from_micros(300 * 1000)); + async_std::task::sleep(time::Duration::from_micros(300 * 1000)).await; } } } diff --git a/src/job_thread.rs b/src/job_thread.rs index 3676eb42e..736f85182 100644 --- a/src/job_thread.rs +++ b/src/job_thread.rs @@ -46,7 +46,7 @@ impl JobThread { if !using_handle { return; } - std::thread::sleep(std::time::Duration::from_micros(300 * 1000)); + async_std::task::sleep(std::time::Duration::from_micros(300 * 1000)).await; } } From 7326ba140303ecf9c536568e490e76d33394fe89 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Sat, 7 Mar 2020 11:21:32 +0100 Subject: [PATCH 005/118] send bounds wip --- Cargo.lock | 5 +- Cargo.toml | 2 +- examples/simple.rs | 38 +++---- rust-toolchain | 2 +- src/constants.rs | 6 +- src/imap/mod.rs | 235 ++++++++++++++++++++++++-------------------- src/imap/session.rs | 90 ++++++++--------- src/job_thread.rs | 4 +- 8 files changed, 194 insertions(+), 188 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9ca9f0566..197cc6aa7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -85,9 +85,7 @@ dependencies = [ [[package]] name = "async-imap" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "async-attributes 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "async-native-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -627,7 +625,7 @@ dependencies = [ name = "deltachat" version = "1.27.0" dependencies = [ - "async-imap 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "async-imap 0.2.0", "async-native-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "async-smtp 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3150,7 +3148,6 @@ dependencies = [ "checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" "checksum ascii_utils 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a" "checksum async-attributes 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "efd3d156917d94862e779f356c5acae312b08fd3121e792c857d7928c8088423" -"checksum async-imap 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46ff8df29e2a90154d85d3c21e843d1f6d9337dcdcf23b3b5a87228d18122c84" "checksum async-native-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d40a615e861c981117e15c28c577daf9918cabd2e2d588a5e06811ae79c9da1a" "checksum async-smtp 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3652e5c6072c0694a2bcdb7e8409980d2676bd4f024adf4aab10c68fd2b48f5" "checksum async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "538ecb01eb64eecd772087e5b6f7540cbc917f047727339a472dafed2185b267" diff --git a/Cargo.toml b/Cargo.toml index eb42deba5..fbdd15f96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ num-traits = "0.2.6" async-smtp = "0.2" email = { git = "https://github.com/deltachat/rust-email", branch = "master" } lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" } -async-imap = "0.2" +async-imap = { path = "../../async-imap"} # "0.2" async-native-tls = "0.3.1" async-std = { version = "1.4", features = ["unstable"] } base64 = "0.11" diff --git a/examples/simple.rs b/examples/simple.rs index cb6ac0435..4686f2114 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -1,7 +1,7 @@ extern crate deltachat; -use std::sync::{Arc, RwLock}; -use std::{thread, time}; +use async_std::sync::{Arc, RwLock}; +use std::time; use tempfile::tempdir; use deltachat::chat; @@ -44,28 +44,28 @@ async fn main() { println!("info: {:#?}", info); let ctx = Arc::new(ctx); - let ctx1 = ctx.clone(); let r1 = running.clone(); - let t1 = thread::spawn(move || { - while *r1.read().unwrap() { - async_std::task::block_on(perform_inbox_jobs(&ctx1)); - if *r1.read().unwrap() { - async_std::task::block_on(perform_inbox_fetch(&ctx1)); - if *r1.read().unwrap() { - async_std::task::block_on(perform_inbox_idle(&ctx1)); + let ctx1 = ctx.clone(); + let t1 = async_std::task::spawn(async move { + while *r1.read().await { + // perform_inbox_jobs(&ctx1).await; + if *r1.read().await { + perform_inbox_fetch(&ctx1).await; + + if *r1.read().await { + // perform_inbox_idle(&ctx1).await; } } } }); - let ctx1 = ctx.clone(); let r1 = running.clone(); - let t2 = thread::spawn(move || { - while *r1.read().unwrap() { - async_std::task::block_on(perform_smtp_jobs(&ctx1)); - if *r1.read().unwrap() { - async_std::task::block_on(perform_smtp_idle(&ctx1)); + let t2 = async_std::task::spawn(async move { + while *r1.read().await { + // perform_smtp_jobs(&ctx1).await; + if *r1.read().await { + // perform_smtp_idle(&ctx1).await; } } }); @@ -105,13 +105,13 @@ async fn main() { println!("stopping threads"); - *running.write().unwrap() = false; + *running.write().await = false; deltachat::job::interrupt_inbox_idle(&ctx).await; deltachat::job::interrupt_smtp_idle(&ctx).await; println!("joining"); - t1.join().unwrap(); - t2.join().unwrap(); + t1.await; + t2.await; println!("closing"); } diff --git a/rust-toolchain b/rust-toolchain index 22e904890..5b1388415 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2019-11-06 +nightly-2020-03-04 diff --git a/src/constants.rs b/src/constants.rs index 45ebe1375..6404b74f7 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -197,13 +197,13 @@ pub const DC_LP_SMTP_SOCKET_SSL: usize = 0x20000; pub const DC_LP_SMTP_SOCKET_PLAIN: usize = 0x40000; /// if none of these flags are set, the default is chosen -pub const DC_LP_AUTH_FLAGS: i32 = (DC_LP_AUTH_OAUTH2 | DC_LP_AUTH_NORMAL); +pub const DC_LP_AUTH_FLAGS: i32 = DC_LP_AUTH_OAUTH2 | DC_LP_AUTH_NORMAL; /// if none of these flags are set, the default is chosen pub const DC_LP_IMAP_SOCKET_FLAGS: i32 = - (DC_LP_IMAP_SOCKET_STARTTLS | DC_LP_IMAP_SOCKET_SSL | DC_LP_IMAP_SOCKET_PLAIN); + DC_LP_IMAP_SOCKET_STARTTLS | DC_LP_IMAP_SOCKET_SSL | DC_LP_IMAP_SOCKET_PLAIN; /// if none of these flags are set, the default is chosen pub const DC_LP_SMTP_SOCKET_FLAGS: usize = - (DC_LP_SMTP_SOCKET_STARTTLS | DC_LP_SMTP_SOCKET_SSL | DC_LP_SMTP_SOCKET_PLAIN); + DC_LP_SMTP_SOCKET_STARTTLS | DC_LP_SMTP_SOCKET_SSL | DC_LP_SMTP_SOCKET_PLAIN; // QR code scanning (view from Bob, the joiner) pub const DC_VC_AUTH_REQUIRED: i32 = 2; diff --git a/src/imap/mod.rs b/src/imap/mod.rs index 32a365051..e520e55a7 100644 --- a/src/imap/mod.rs +++ b/src/imap/mod.rs @@ -11,6 +11,7 @@ use async_imap::{ error::Result as ImapResult, types::{Capability, Fetch, Flag, Mailbox, Name, NameAttribute}, }; +use async_std::prelude::*; use async_std::sync::{Mutex, RwLock}; use crate::config::*; @@ -555,7 +556,13 @@ impl Imap { // last-index message. let set = format!("{}", mailbox.exists); match session.fetch(set, JUST_UID).await { - Ok(list) => list[0].uid.unwrap_or_default(), + Ok(mut list) => { + if let Some(Ok(msg)) = list.next().await { + msg.uid.unwrap_or_default() + } else { + return Err(Error::Other("failed to fetch".into())); + } + } Err(err) => { return Err(Error::FetchFailed(err)); } @@ -592,88 +599,89 @@ impl Imap { let mut read_cnt: usize = 0; - let mut list = if let Some(ref mut session) = &mut *self.session.lock().await { + // prefetch info from all unfetched mails + let mut new_last_seen_uid = last_seen_uid; + let mut read_errors = 0; + + if let Some(ref mut session) = &mut *self.session.lock().await { // fetch messages with larger UID than the last one seen // `(UID FETCH lastseenuid+1:*)`, see RFC 4549 let set = format!("{}:*", last_seen_uid + 1); - match session.uid_fetch(set, PREFETCH_FLAGS).await { + let mut list = match session.uid_fetch(set, PREFETCH_FLAGS).await { Ok(list) => list, Err(err) => { return Err(Error::FetchFailed(err)); } + }; + + while let Some(fetch) = list.next().await { + let fetch = fetch.map_err(|err| Error::Other(err.to_string()))?; + let cur_uid = fetch.uid.unwrap_or_default(); + if cur_uid <= last_seen_uid { + // If the mailbox is not empty, results always include + // at least one UID, even if last_seen_uid+1 is past + // the last UID in the mailbox. It happens because + // uid+1:* is interpreted the same way as *:uid+1. + // See https://tools.ietf.org/html/rfc3501#page-61 for + // standard reference. Therefore, sometimes we receive + // already seen messages and have to filter them out. + info!( + context, + "fetch_new_messages: ignoring uid {}, last seen was {}", + cur_uid, + last_seen_uid + ); + continue; + } + read_cnt += 1; + + let headers = get_fetch_headers(&fetch)?; + let message_id = prefetch_get_message_id(&headers).unwrap_or_default(); + if precheck_imf(context, &message_id, folder.as_ref(), cur_uid).await { + // we know the message-id already or don't want the message otherwise. + info!( + context, + "Skipping message {} from \"{}\" by precheck.", + message_id, + folder.as_ref(), + ); + } else { + let show = prefetch_should_download(context, &headers, show_emails) + .map_err(|err| { + warn!(context, "prefetch_should_download error: {}", err); + err + }) + .unwrap_or(true); + + if !show { + info!( + context, + "Ignoring new message {} from \"{}\".", + message_id, + folder.as_ref(), + ); + } else { + // check passed, go fetch the rest + if let Err(err) = self.fetch_single_msg(context, &folder, cur_uid).await { + info!( + context, + "Read error for message {} from \"{}\", trying over later: {}.", + message_id, + folder.as_ref(), + err + ); + read_errors += 1; + } + } + } + if read_errors == 0 { + new_last_seen_uid = cur_uid; + } } } else { return Err(Error::NoConnection); }; - // prefetch info from all unfetched mails - let mut new_last_seen_uid = last_seen_uid; - let mut read_errors = 0; - - list.sort_unstable_by_key(|msg| msg.uid.unwrap_or_default()); - - for fetch in &list { - let cur_uid = fetch.uid.unwrap_or_default(); - if cur_uid <= last_seen_uid { - // If the mailbox is not empty, results always include - // at least one UID, even if last_seen_uid+1 is past - // the last UID in the mailbox. It happens because - // uid+1:* is interpreted the same way as *:uid+1. - // See https://tools.ietf.org/html/rfc3501#page-61 for - // standard reference. Therefore, sometimes we receive - // already seen messages and have to filter them out. - info!( - context, - "fetch_new_messages: ignoring uid {}, last seen was {}", cur_uid, last_seen_uid - ); - continue; - } - read_cnt += 1; - - let headers = get_fetch_headers(fetch)?; - let message_id = prefetch_get_message_id(&headers).unwrap_or_default(); - if precheck_imf(context, &message_id, folder.as_ref(), cur_uid).await { - // we know the message-id already or don't want the message otherwise. - info!( - context, - "Skipping message {} from \"{}\" by precheck.", - message_id, - folder.as_ref(), - ); - } else { - let show = prefetch_should_download(context, &headers, show_emails) - .map_err(|err| { - warn!(context, "prefetch_should_download error: {}", err); - err - }) - .unwrap_or(true); - - if !show { - info!( - context, - "Ignoring new message {} from \"{}\".", - message_id, - folder.as_ref(), - ); - } else { - // check passed, go fetch the rest - if let Err(err) = self.fetch_single_msg(context, &folder, cur_uid).await { - info!( - context, - "Read error for message {} from \"{}\", trying over later: {}.", - message_id, - folder.as_ref(), - err - ); - read_errors += 1; - } - } - } - if read_errors == 0 { - new_last_seen_uid = cur_uid; - } - } - if new_last_seen_uid > last_seen_uid { self.set_config_last_seen_uid(context, &folder, uid_validity, new_last_seen_uid); } @@ -728,7 +736,8 @@ impl Imap { let set = format!("{}", server_uid); - let msgs = if let Some(ref mut session) = &mut *self.session.lock().await { + let mut session_lock = self.session.lock().await; + let mut msgs = if let Some(ref mut session) = &mut *session_lock { match session.uid_fetch(set, BODY_FLAGS).await { Ok(msgs) => msgs, Err(err) => { @@ -751,7 +760,7 @@ impl Imap { return Err(Error::Other("Could not get IMAP session".to_string())); }; - if let Some(msg) = msgs.first() { + if let Some(Ok(msg)) = msgs.next().await { // XXX put flags into a set and pass them to dc_receive_imf let is_deleted = msg.flags().any(|flag| flag == Flag::Deleted); let is_seen = msg.flags().any(|flag| flag == Flag::Seen); @@ -1006,8 +1015,8 @@ impl Imap { // this comes at the expense of another imap query if let Some(ref mut session) = &mut *self.session.lock().await { match session.uid_fetch(set, DELETE_CHECK_FLAGS).await { - Ok(msgs) => { - let fetch = if let Some(fetch) = msgs.first() { + Ok(mut msgs) => { + let fetch = if let Some(Ok(fetch)) = msgs.next().await { fetch } else { warn!( @@ -1019,7 +1028,7 @@ impl Imap { return ImapActionResult::Failed; }; - let remote_message_id = get_fetch_headers(fetch) + let remote_message_id = get_fetch_headers(&fetch) .and_then(|headers| prefetch_get_message_id(&headers)) .unwrap_or_default(); @@ -1081,32 +1090,42 @@ impl Imap { if !self.is_connected().await { return Err(Error::NoConnection); } - info!(context, "Configuring IMAP-folders."); if let Some(ref mut session) = &mut *self.session.lock().await { - let folders = match self.list_folders(session, context).await { - Some(f) => f, - None => { - return Err(Error::Other("list_folders failed".to_string())); + let mut folders = match session.list(Some(""), Some("*")).await { + Ok(f) => f, + Err(err) => { + return Err(Error::Other(format!("list_folders failed {:?}", err))); } }; - let sentbox_folder = folders - .iter() - .find(|folder| match get_folder_meaning(folder) { - FolderMeaning::SentObjects => true, - _ => false, - }); - info!(context, "sentbox folder is {:?}", sentbox_folder); - + let mut sentbox_folder = None; + let mut mvbox_folder = None; let delimiter = self.config.read().await.imap_delimiter; let fallback_folder = format!("INBOX{}DeltaChat", delimiter); - let mut mvbox_folder = folders - .iter() - .find(|folder| folder.name() == "DeltaChat" || folder.name() == fallback_folder) - .map(|n| n.name().to_string()); + while let Some(folder) = folders.next().await { + let folder = folder.map_err(|err| Error::Other(err.to_string()))?; + + if folder.name() == "DeltaChat" || folder.name() == fallback_folder { + mvbox_folder = Some(folder.name().to_string()); + } + let is_sentbox_folder = match get_folder_meaning(&folder) { + FolderMeaning::SentObjects => true, + _ => false, + }; + if is_sentbox_folder { + info!(context, "sentbox folder is {:?}", folder); + sentbox_folder = Some(folder); + } + + if mvbox_folder.is_some() && sentbox_folder.is_some() { + break; + } + } + + drop(folders); if mvbox_folder.is_none() && create_mvbox { info!(context, "Creating MVBOX-folder \"DeltaChat\"...",); @@ -1172,22 +1191,22 @@ impl Imap { Ok(()) } - async fn list_folders(&self, session: &mut Session, context: &Context) -> Option> { - match session.list(Some(""), Some("*")).await { - Ok(list) => { - if list.is_empty() { - warn!(context, "Folder list is empty.",); - } - Some(list) - } - Err(err) => { - eprintln!("list error: {:?}", err); - warn!(context, "Cannot get folder list.",); + // async fn list_folders(&self, session: &mut Session, context: &Context) -> Option> { + // match session.list(Some(""), Some("*")).await { + // Ok(list) => { + // if list.is_empty() { + // warn!(context, "Folder list is empty.",); + // } + // Some(list) + // } + // Err(err) => { + // eprintln!("list error: {:?}", err); + // warn!(context, "Cannot get folder list.",); - None - } - } - } + // None + // } + // } + // } pub async fn empty_folder(&self, context: &Context, folder: &str) { info!(context, "emptying folder {}", folder); diff --git a/src/imap/session.rs b/src/imap/session.rs index c4f9d0a95..8fb146874 100644 --- a/src/imap/session.rs +++ b/src/imap/session.rs @@ -27,22 +27,19 @@ impl Session { &mut self, reference_name: Option<&str>, mailbox_pattern: Option<&str>, - ) -> ImapResult> { - let res = match self { + ) -> ImapResult> + '_ + Send + Unpin> { + match self { Session::Secure(i) => { - i.list(reference_name, mailbox_pattern) - .await? - .collect::>() - .await? + i.list(reference_name, mailbox_pattern).await + // list.collect::>().await? } Session::Insecure(i) => { - i.list(reference_name, mailbox_pattern) - .await? - .collect::>() - .await? + unimplemented!() + // i.list(reference_name, mailbox_pattern).await + // .collect::>() + // .await? } - }; - Ok(res) + } } pub async fn create>(&mut self, mailbox_name: S) -> ImapResult<()> { @@ -78,68 +75,59 @@ impl Session { Ok(mbox) } - pub async fn fetch(&mut self, sequence_set: S1, query: S2) -> ImapResult> + pub async fn fetch<'a, S1, S2>( + &'a mut self, + sequence_set: S1, + query: S2, + ) -> ImapResult> + 'a + Send + Unpin> where - S1: AsRef, - S2: AsRef, + S1: 'a + AsRef, + S2: 'a + AsRef, { let res = match self { - Session::Secure(i) => { - i.fetch(sequence_set, query) - .await? - .collect::>() - .await? - } + Session::Secure(i) => i.fetch(sequence_set, query).await?, Session::Insecure(i) => { - i.fetch(sequence_set, query) - .await? - .collect::>() - .await? + unimplemented!() + // i.fetch(sequence_set, query).await? } }; Ok(res) } - pub async fn uid_fetch(&mut self, uid_set: S1, query: S2) -> ImapResult> + pub async fn uid_fetch<'a, S1, S2>( + &'a mut self, + uid_set: S1, + query: S2, + ) -> ImapResult> + 'a + Send + Unpin> where - S1: AsRef, - S2: AsRef, + S1: 'a + AsRef, + S2: 'a + AsRef, { let res = match self { - Session::Secure(i) => { - i.uid_fetch(uid_set, query) - .await? - .collect::>() - .await? - } + Session::Secure(i) => i.uid_fetch(uid_set, query).await?, Session::Insecure(i) => { - i.uid_fetch(uid_set, query) - .await? - .collect::>() - .await? + unimplemented!() + // i.uid_fetch(uid_set, query).await? } }; Ok(res) } - pub async fn uid_store(&mut self, uid_set: S1, query: S2) -> ImapResult> + pub async fn uid_store<'a, S1, S2>( + &'a mut self, + uid_set: S1, + query: S2, + ) -> ImapResult> + 'a + Send + Unpin> where - S1: AsRef, - S2: AsRef, + S1: 'a + AsRef, + S2: 'a + AsRef, { let res = match self { - Session::Secure(i) => { - i.uid_store(uid_set, query) - .await? - .collect::>() - .await? - } + Session::Secure(i) => i.uid_store(uid_set, query).await?, Session::Insecure(i) => { - i.uid_store(uid_set, query) - .await? - .collect::>() - .await? + unimplemented!() + // i.uid_store(uid_set, query).await? } }; Ok(res) diff --git a/src/job_thread.rs b/src/job_thread.rs index 736f85182..a39c34f44 100644 --- a/src/job_thread.rs +++ b/src/job_thread.rs @@ -106,7 +106,9 @@ impl JobThread { } } } - self.state.lock().await.using_handle = false; + { + self.state.lock().await.using_handle = false; + } } async fn connect_and_fetch(&self, context: &Context) -> Result<()> { From 6ea1d665bb0f5d22e39a4a9259724bec7a02c335 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Sat, 7 Mar 2020 18:54:09 +0100 Subject: [PATCH 006/118] start making sql async --- examples/simple.rs | 2 +- src/chat.rs | 1452 ++++++++++++++++++++---------------- src/chatlist.rs | 152 ++-- src/config.rs | 39 +- src/configure/mod.rs | 26 +- src/contact.rs | 343 +++++---- src/context.rs | 118 +-- src/dc_receive_imf.rs | 114 +-- src/e2ee.rs | 64 +- src/imap/mod.rs | 61 +- src/imex.rs | 306 ++++---- src/job.rs | 343 +++++---- src/job_thread.rs | 14 +- src/key.rs | 11 +- src/keyring.rs | 3 +- src/location.rs | 326 +++++---- src/login_param.rs | 69 +- src/message.rs | 201 ++--- src/mimefactory.rs | 3 +- src/mimeparser.rs | 17 +- src/peerstate.rs | 24 +- src/qr.rs | 9 +- src/securejoin.rs | 106 +-- src/sql.rs | 1624 ++++++++++++++++++++++------------------- src/stock.rs | 6 +- src/test_utils.rs | 1 + src/token.rs | 44 +- 27 files changed, 3057 insertions(+), 2421 deletions(-) diff --git a/examples/simple.rs b/examples/simple.rs index 4686f2114..38b143fc5 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -49,7 +49,7 @@ async fn main() { let ctx1 = ctx.clone(); let t1 = async_std::task::spawn(async move { while *r1.read().await { - // perform_inbox_jobs(&ctx1).await; + perform_inbox_jobs(&ctx1).await; if *r1.read().await { perform_inbox_fetch(&ctx1).await; diff --git a/src/chat.rs b/src/chat.rs index a111cdb0a..e81589fcc 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -108,28 +108,36 @@ impl ChatId { self.0 == DC_CHAT_ID_ALLDONE_HINT } - pub fn set_selfavatar_timestamp(self, context: &Context, timestamp: i64) -> Result<(), Error> { - context.sql.execute( - "UPDATE contacts + pub async fn set_selfavatar_timestamp( + self, + context: &Context, + timestamp: i64, + ) -> Result<(), Error> { + context + .sql + .execute( + "UPDATE contacts SET selfavatar_sent=? WHERE id IN(SELECT contact_id FROM chats_contacts WHERE chat_id=?);", - params![timestamp, self], - )?; + params![timestamp, self], + ) + .await?; Ok(()) } - pub fn set_blocked(self, context: &Context, new_blocked: Blocked) -> bool { + pub async fn set_blocked(self, context: &Context, new_blocked: Blocked) -> bool { if self.is_special() { warn!(context, "ignoring setting of Block-status for {}", self); return false; } - sql::execute( - context, - &context.sql, - "UPDATE chats SET blocked=? WHERE id=?;", - params![new_blocked, self], - ) - .is_ok() + context + .sql + .execute( + "UPDATE chats SET blocked=? WHERE id=?;", + params![new_blocked, self], + ) + .await + .is_ok() } pub fn unblock(self, context: &Context) { @@ -137,7 +145,7 @@ impl ChatId { } /// Archives or unarchives a chat. - pub fn set_visibility( + pub async fn set_visibility( self, context: &Context, visibility: ChatVisibility, @@ -149,20 +157,22 @@ impl ChatId { ); if visibility == ChatVisibility::Archived { - sql::execute( - context, - &context.sql, - "UPDATE msgs SET state=? WHERE chat_id=? AND state=?;", - params![MessageState::InNoticed, self, MessageState::InFresh], - )?; + context + .sql + .execute( + "UPDATE msgs SET state=? WHERE chat_id=? AND state=?;", + params![MessageState::InNoticed, self, MessageState::InFresh], + ) + .await?; } - sql::execute( - context, - &context.sql, - "UPDATE chats SET archived=? WHERE id=?;", - params![visibility, self], - )?; + context + .sql + .execute( + "UPDATE chats SET archived=? WHERE id=?;", + params![visibility, self], + ) + .await?; context.call_cb(Event::MsgsChanged { msg_id: MsgId::new(0), @@ -174,13 +184,14 @@ impl ChatId { // note that unarchive() is not the same as set_visibility(Normal) - // eg. unarchive() does not modify pinned chats and does not send events. - pub fn unarchive(self, context: &Context) -> Result<(), Error> { - sql::execute( - context, - &context.sql, - "UPDATE chats SET archived=0 WHERE id=? and archived=1", - params![self], - )?; + pub async fn unarchive(self, context: &Context) -> Result<(), Error> { + context + .sql + .execute( + "UPDATE chats SET archived=0 WHERE id=? and archived=1", + params![self], + ) + .await?; Ok(()) } @@ -193,34 +204,29 @@ impl ChatId { ); /* Up to 2017-11-02 deleting a group also implied leaving it, see above why we have changed this. */ - let _chat = Chat::load_from_db(context, self)?; - sql::execute( - context, - &context.sql, - "DELETE FROM msgs_mdns WHERE msg_id IN (SELECT id FROM msgs WHERE chat_id=?);", - params![self], - )?; + let _chat = Chat::load_from_db(context, self).await?; + context + .sql + .execute( + "DELETE FROM msgs_mdns WHERE msg_id IN (SELECT id FROM msgs WHERE chat_id=?);", + params![self], + ) + .await?; - sql::execute( - context, - &context.sql, - "DELETE FROM msgs WHERE chat_id=?;", - params![self], - )?; + context + .sql + .execute("DELETE FROM msgs WHERE chat_id=?;", params![self]) + .await?; - sql::execute( - context, - &context.sql, - "DELETE FROM chats_contacts WHERE chat_id=?;", - params![self], - )?; + context + .sql + .execute("DELETE FROM chats_contacts WHERE chat_id=?;", params![self]) + .await?; - sql::execute( - context, - &context.sql, - "DELETE FROM chats WHERE id=?;", - params![self], - )?; + context + .sql + .execute("DELETE FROM chats WHERE id=?;", params![self]) + .await?; context.call_cb(Event::MsgsChanged { msg_id: MsgId::new(0), @@ -236,14 +242,14 @@ impl ChatId { /// Sets draft message. /// /// Passing `None` as message just deletes the draft - pub fn set_draft(self, context: &Context, msg: Option<&mut Message>) { + pub async fn set_draft(self, context: &Context, msg: Option<&mut Message>) { if self.is_special() { return; } let changed = match msg { - None => self.maybe_delete_draft(context), - Some(msg) => self.set_draft_raw(context, msg), + None => self.maybe_delete_draft(context).await, + Some(msg) => self.set_draft_raw(context, msg).await, }; if changed { @@ -255,28 +261,34 @@ impl ChatId { } // similar to as dc_set_draft() but does not emit an event - fn set_draft_raw(self, context: &Context, msg: &mut Message) -> bool { - let deleted = self.maybe_delete_draft(context); - let set = self.do_set_draft(context, msg).is_ok(); + async fn set_draft_raw(self, context: &Context, msg: &mut Message) -> bool { + let deleted = self.maybe_delete_draft(context).await; + let set = self.do_set_draft(context, msg).await.is_ok(); // Can't inline. Both functions above must be called, no shortcut! deleted || set } - fn get_draft_msg_id(self, context: &Context) -> Option { - context.sql.query_get_value::<_, MsgId>( - context, - "SELECT id FROM msgs WHERE chat_id=? AND state=?;", - params![self, MessageState::OutDraft], - ) + async fn get_draft_msg_id(self, context: &Context) -> Option { + context + .sql + .query_get_value::<_, MsgId>( + context, + "SELECT id FROM msgs WHERE chat_id=? AND state=?;", + params![self, MessageState::OutDraft], + ) + .await } - pub fn get_draft(self, context: &Context) -> Result, Error> { + pub async fn get_draft(self, context: &Context) -> Result, Error> { if self.is_special() { return Ok(None); } - match self.get_draft_msg_id(context) { - Some(draft_msg_id) => Ok(Some(Message::load_from_db(context, draft_msg_id)?)), + match self.get_draft_msg_id(context).await { + Some(draft_msg_id) => { + let msg = Message::load_from_db(context, draft_msg_id).await?; + Ok(Some(msg)) + } None => Ok(None), } } @@ -284,8 +296,8 @@ impl ChatId { /// Delete draft message in specified chat, if there is one. /// /// Returns `true`, if message was deleted, `false` otherwise. - fn maybe_delete_draft(self, context: &Context) -> bool { - match self.get_draft_msg_id(context) { + async fn maybe_delete_draft(self, context: &Context) -> bool { + match self.get_draft_msg_id(context).await { Some(msg_id) => { Message::delete_from_db(context, msg_id); true @@ -297,7 +309,7 @@ impl ChatId { /// Set provided message as draft message for specified chat. /// /// Return true on success, false on database error. - fn do_set_draft(self, context: &Context, msg: &mut Message) -> Result<(), Error> { + async fn do_set_draft(self, context: &Context, msg: &mut Message) -> Result<(), Error> { match msg.viewtype { Viewtype::Unknown => bail!("Can not set draft of unknown type."), Viewtype::Text => match msg.text.as_ref() { @@ -316,27 +328,29 @@ impl ChatId { msg.param.set(Param::File, blob.as_name()); } } - sql::execute( - context, - &context.sql, - "INSERT INTO msgs (chat_id, from_id, timestamp, type, state, txt, param, hidden) + context + .sql + .execute( + "INSERT INTO msgs (chat_id, from_id, timestamp, type, state, txt, param, hidden) VALUES (?,?,?, ?,?,?,?,?);", - params![ - self, - DC_CONTACT_ID_SELF, - time(), - msg.viewtype, - MessageState::OutDraft, - msg.text.as_ref().map(String::as_str).unwrap_or(""), - msg.param.to_string(), - 1, - ], - )?; + params![ + self, + DC_CONTACT_ID_SELF, + time(), + msg.viewtype, + MessageState::OutDraft, + msg.text.as_ref().map(String::as_str).unwrap_or(""), + msg.param.to_string(), + 1, + ], + ) + .await?; + Ok(()) } /// Returns number of messages in a chat. - pub fn get_msg_cnt(self, context: &Context) -> usize { + pub async fn get_msg_cnt(self, context: &Context) -> usize { context .sql .query_get_value::<_, i32>( @@ -344,21 +358,23 @@ impl ChatId { "SELECT COUNT(*) FROM msgs WHERE chat_id=?;", params![self], ) + .await .unwrap_or_default() as usize } - pub fn get_fresh_msg_cnt(self, context: &Context) -> usize { + pub async fn get_fresh_msg_cnt(self, context: &Context) -> usize { context .sql .query_get_value::<_, i32>( context, "SELECT COUNT(*) - FROM msgs - WHERE state=10 + FROM msgs + WHERE state=10 AND hidden=0 AND chat_id=?;", params![self], ) + .await .unwrap_or_default() as usize } @@ -435,28 +451,31 @@ pub struct Chat { impl Chat { /// Loads chat from the database by its ID. - pub fn load_from_db(context: &Context, chat_id: ChatId) -> Result { - let res = context.sql.query_row( - "SELECT c.type, c.name, c.grpid, c.param, c.archived, + pub async fn load_from_db(context: &Context, chat_id: ChatId) -> Result { + let res = context + .sql + .query_row( + "SELECT c.type, c.name, c.grpid, c.param, c.archived, c.blocked, c.locations_send_until, c.muted_until FROM chats c WHERE c.id=?;", - params![chat_id], - |row| { - let c = Chat { - id: chat_id, - typ: row.get(0)?, - name: row.get::<_, String>(1)?, - grpid: row.get::<_, String>(2)?, - param: row.get::<_, String>(3)?.parse().unwrap_or_default(), - visibility: row.get(4)?, - blocked: row.get::<_, Option<_>>(5)?.unwrap_or_default(), - is_sending_locations: row.get(6)?, - mute_duration: row.get(7)?, - }; - Ok(c) - }, - ); + params![chat_id], + |row| { + let c = Chat { + id: chat_id, + typ: row.get(0)?, + name: row.get::<_, String>(1)?, + grpid: row.get::<_, String>(2)?, + param: row.get::<_, String>(3)?.parse().unwrap_or_default(), + visibility: row.get(4)?, + blocked: row.get::<_, Option<_>>(5)?.unwrap_or_default(), + is_sending_locations: row.get(6)?, + mute_duration: row.get(7)?, + }; + Ok(c) + }, + ) + .await; match res { Err(err @ crate::sql::Error::Sql(rusqlite::Error::QueryReturnedNoRows)) => { @@ -474,16 +493,16 @@ impl Chat { chat.name = context.stock_str(StockMessage::DeadDrop).into(); } else if chat.id.is_archived_link() { let tempname = context.stock_str(StockMessage::ArchivedChats); - let cnt = dc_get_archived_cnt(context); + let cnt = dc_get_archived_cnt(context).await; chat.name = format!("{} ({})", tempname, cnt); } else if chat.id.is_starred() { chat.name = context.stock_str(StockMessage::StarredMsgs).into(); } else { if chat.typ == Chattype::Single { - let contacts = get_chat_contacts(context, chat.id); + let contacts = get_chat_contacts(context, chat.id).await; let mut chat_name = "Err [Name not found]".to_owned(); if let Some(contact_id) = contacts.first() { - if let Ok(contact) = Contact::get_by_id(context, *contact_id) { + if let Ok(contact) = Contact::get_by_id(context, *contact_id).await { chat_name = contact.get_display_name().to_owned(); } } @@ -514,13 +533,14 @@ impl Chat { !self.id.is_special() && !self.is_device_talk() } - pub fn update_param(&mut self, context: &Context) -> Result<(), Error> { - sql::execute( - context, - &context.sql, - "UPDATE chats SET param=? WHERE id=?", - params![self.param.to_string(), self.id], - )?; + pub async fn update_param(&mut self, context: &Context) -> Result<(), Error> { + context + .sql + .execute( + "UPDATE chats SET param=? WHERE id=?", + params![self.param.to_string(), self.id], + ) + .await?; Ok(()) } @@ -539,7 +559,7 @@ impl Chat { &self.name } - pub fn get_subtitle(&self, context: &Context) -> String { + pub async fn get_subtitle(&self, context: &Context) -> String { // returns either the address or the number of chat members if self.typ == Chattype::Single && self.param.exists(Param::Selftalk) { @@ -557,6 +577,7 @@ impl Chat { WHERE cc.chat_id=?;", params![self.id], ) + .await .unwrap_or_else(|| "Err".into()); } @@ -564,7 +585,7 @@ impl Chat { if self.id.is_deaddrop() { return context.stock_str(StockMessage::DeadDrop).into(); } - let cnt = get_chat_contact_cnt(context, self.id); + let cnt = get_chat_contact_cnt(context, self.id).await; return context.stock_string_repl_int(StockMessage::Member, cnt as i32); } @@ -583,22 +604,22 @@ impl Chat { ) } - fn get_parent_mime_headers(&self, context: &Context) -> Option<(String, String, String)> { + async fn get_parent_mime_headers(&self, context: &Context) -> Option<(String, String, String)> { let collect = |row: &rusqlite::Row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)); let params = params![self.id]; let sql = &context.sql; let query = Self::parent_query("rfc724_mid, mime_in_reply_to, mime_references"); - sql.query_row(&query, params, collect).ok() + sql.query_row(&query, params, collect).await.ok() } - fn parent_is_encrypted(&self, context: &Context) -> Result { + async fn parent_is_encrypted(&self, context: &Context) -> Result { let sql = &context.sql; let params = params![self.id]; let query = Self::parent_query("param"); - let packed: Option = sql.query_get_value_result(&query, params)?; + let packed: Option = sql.query_get_value_result(&query, params).await?; if let Some(ref packed) = packed { let param = packed.parse::()?; @@ -609,16 +630,16 @@ impl Chat { } } - pub fn get_profile_image(&self, context: &Context) -> Option { + pub async fn get_profile_image(&self, context: &Context) -> Option { if let Some(image_rel) = self.param.get(Param::ProfileImage) { if !image_rel.is_empty() { return Some(dc_get_abs_path(context, image_rel)); } } else if self.typ == Chattype::Single { - let contacts = get_chat_contacts(context, self.id); + let contacts = get_chat_contacts(context, self.id).await; if let Some(contact_id) = contacts.first() { - if let Ok(contact) = Contact::get_by_id(context, *contact_id) { - return contact.get_profile_image(context); + if let Ok(contact) = Contact::get_by_id(context, *contact_id).await { + return contact.get_profile_image(context).await; } } } @@ -626,17 +647,17 @@ impl Chat { None } - pub fn get_gossiped_timestamp(&self, context: &Context) -> i64 { - get_gossiped_timestamp(context, self.id) + pub async fn get_gossiped_timestamp(&self, context: &Context) -> i64 { + get_gossiped_timestamp(context, self.id).await } - pub fn get_color(&self, context: &Context) -> u32 { + pub async fn get_color(&self, context: &Context) -> u32 { let mut color = 0; if self.typ == Chattype::Single { - let contacts = get_chat_contacts(context, self.id); + let contacts = get_chat_contacts(context, self.id).await; if let Some(contact_id) = contacts.first() { - if let Ok(contact) = Contact::get_by_id(context, *contact_id) { + if let Ok(contact) = Contact::get_by_id(context, *contact_id).await { color = contact.get_color(); } } @@ -651,8 +672,8 @@ impl Chat { /// /// This is somewhat experimental, even more so than the rest of /// deltachat, and the data returned is still subject to change. - pub fn get_info(&self, context: &Context) -> Result { - let draft = match self.id.get_draft(context)? { + pub async fn get_info(&self, context: &Context) -> Result { + let draft = match self.id.get_draft(context).await? { Some(message) => message.text.unwrap_or_else(String::new), _ => String::new(), }; @@ -662,11 +683,14 @@ impl Chat { name: self.name.clone(), archived: self.visibility == ChatVisibility::Archived, param: self.param.to_string(), - gossiped_timestamp: self.get_gossiped_timestamp(context), + gossiped_timestamp: self.get_gossiped_timestamp(context).await, is_sending_locations: self.is_sending_locations, - color: self.get_color(context), - profile_image: self.get_profile_image(context).unwrap_or_else(PathBuf::new), - subtitle: self.get_subtitle(context), + color: self.get_color(context).await, + profile_image: self + .get_profile_image(context) + .await + .unwrap_or_else(PathBuf::new), + subtitle: self.get_subtitle(context).await, draft, is_muted: self.is_muted(), }) @@ -702,7 +726,7 @@ impl Chat { } } - fn prepare_msg_raw( + async fn prepare_msg_raw( &mut self, context: &Context, msg: &mut Message, @@ -723,7 +747,7 @@ impl Chat { } if (self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup) - && !is_contact_in_chat(context, self.id, DC_CONTACT_ID_SELF) + && !is_contact_in_chat(context, self.id, DC_CONTACT_ID_SELF).await { emit_event!( context, @@ -732,7 +756,7 @@ impl Chat { bail!("Cannot set message; self not in group."); } - if let Some(from) = context.get_config(Config::ConfiguredAddr) { + if let Some(from) = context.get_config(Config::ConfiguredAddr).await { let new_rfc724_mid = { let grpid = match self.typ { Chattype::Group | Chattype::VerifiedGroup => Some(self.grpid.as_str()), @@ -742,11 +766,15 @@ impl Chat { }; if self.typ == Chattype::Single { - if let Some(id) = context.sql.query_get_value( - context, - "SELECT contact_id FROM chats_contacts WHERE chat_id=?;", - params![self.id], - ) { + if let Some(id) = context + .sql + .query_get_value( + context, + "SELECT contact_id FROM chats_contacts WHERE chat_id=?;", + params![self.id], + ) + .await + { to_id = id; } else { error!( @@ -760,7 +788,7 @@ impl Chat { { msg.param.set_int(Param::AttachGroupImage, 1); self.param.remove(Param::Unpromoted); - self.update_param(context)?; + self.update_param(context).await?; } /* check if we want to encrypt this message. If yes and circumstances change @@ -768,46 +796,49 @@ impl Chat { we might not send the message out at all */ if msg.param.get_int(Param::ForcePlaintext).unwrap_or_default() == 0 { let mut can_encrypt = true; - let mut all_mutual = context.get_config_bool(Config::E2eeEnabled); + let mut all_mutual = context.get_config_bool(Config::E2eeEnabled).await; // take care that this statement returns NULL rows // if there is no peerstates for a chat member! // for DC_PARAM_SELFTALK this statement does not return any row - let res = context.sql.query_map( - "SELECT ps.prefer_encrypted, c.addr \ + let res = context + .sql + .query_map( + "SELECT ps.prefer_encrypted, c.addr \ FROM chats_contacts cc \ LEFT JOIN contacts c ON cc.contact_id=c.id \ LEFT JOIN acpeerstates ps ON c.addr=ps.addr \ WHERE cc.chat_id=? AND cc.contact_id>9;", - params![self.id], - |row| { - let addr: String = row.get(1)?; + params![self.id], + |row| { + let addr: String = row.get(1)?; - if let Some(prefer_encrypted) = row.get::<_, Option>(0)? { - // the peerstate exist, so we have either public_key or gossip_key - // and can encrypt potentially - if prefer_encrypted != 1 { - info!( - context, - "[autocrypt] peerstate for {} is {}", - addr, - if prefer_encrypted == 0 { - "NOPREFERENCE" - } else { - "RESET" - }, - ); + if let Some(prefer_encrypted) = row.get::<_, Option>(0)? { + // the peerstate exist, so we have either public_key or gossip_key + // and can encrypt potentially + if prefer_encrypted != 1 { + info!( + context, + "[autocrypt] peerstate for {} is {}", + addr, + if prefer_encrypted == 0 { + "NOPREFERENCE" + } else { + "RESET" + }, + ); + all_mutual = false; + } + } else { + info!(context, "[autocrypt] no peerstate for {}", addr,); + can_encrypt = false; all_mutual = false; } - } else { - info!(context, "[autocrypt] no peerstate for {}", addr,); - can_encrypt = false; - all_mutual = false; - } - Ok(()) - }, - |rows| rows.collect::, _>>().map_err(Into::into), - ); + Ok(()) + }, + |rows| rows.collect::, _>>().map_err(Into::into), + ) + .await; match res { Ok(_) => {} Err(err) => { @@ -815,7 +846,7 @@ impl Chat { } } - if can_encrypt && (all_mutual || self.parent_is_encrypted(context)?) { + if can_encrypt && (all_mutual || self.parent_is_encrypted(context).await?) { msg.param.set_int(Param::GuaranteeE2ee, 1); } } @@ -830,7 +861,7 @@ impl Chat { // we do not set In-Reply-To/References in this case. if !self.is_self_talk() { if let Some((parent_rfc724_mid, parent_in_reply_to, parent_references)) = - self.get_parent_mime_headers(context) + self.get_parent_mime_headers(context).await { if !parent_rfc724_mid.is_empty() { new_in_reply_to = parent_rfc724_mid.clone(); @@ -860,38 +891,39 @@ impl Chat { // add independent location to database if msg.param.exists(Param::SetLatitude) - && sql::execute( - context, - &context.sql, - "INSERT INTO locations \ + && context + .sql + .execute( + "INSERT INTO locations \ (timestamp,from_id,chat_id, latitude,longitude,independent)\ VALUES (?,?,?, ?,?,1);", // 1=DC_CONTACT_ID_SELF - params![ - timestamp, - DC_CONTACT_ID_SELF, - self.id, - msg.param.get_float(Param::SetLatitude).unwrap_or_default(), - msg.param.get_float(Param::SetLongitude).unwrap_or_default(), - ], - ) - .is_ok() + params![ + timestamp, + DC_CONTACT_ID_SELF, + self.id, + msg.param.get_float(Param::SetLatitude).unwrap_or_default(), + msg.param.get_float(Param::SetLongitude).unwrap_or_default(), + ], + ) + .await + .is_ok() { - location_id = sql::get_rowid2( - context, - &context.sql, - "locations", - "timestamp", - timestamp, - "from_id", - DC_CONTACT_ID_SELF as i32, - ); + location_id = context + .sql + .get_rowid2( + context, + "locations", + "timestamp", + timestamp, + "from_id", + DC_CONTACT_ID_SELF as i32, + ) + .await?; } // add message to the database - if sql::execute( - context, - &context.sql, + if context.sql.execute( "INSERT INTO msgs (rfc724_mid, chat_id, from_id, to_id, timestamp, type, state, txt, param, hidden, mime_in_reply_to, mime_references, location_id) VALUES (?,?,?,?,?, ?,?,?,?,?, ?,?,?);", params![ new_rfc724_mid, @@ -908,14 +940,13 @@ impl Chat { new_references, location_id as i32, ] - ).is_ok() { - msg_id = sql::get_rowid( + ).await.is_ok() { + msg_id = context.sql.get_rowid( context, - &context.sql, "msgs", "rfc724_mid", new_rfc724_mid, - ); + ).await?; } else { error!( context, @@ -1054,9 +1085,9 @@ pub struct ChatInfo { /// # Returns /// /// The "created" chat ID is returned. -pub fn create_by_msg_id(context: &Context, msg_id: MsgId) -> Result { - let msg = Message::load_from_db(context, msg_id)?; - let chat = Chat::load_from_db(context, msg.chat_id)?; +pub async fn create_by_msg_id(context: &Context, msg_id: MsgId) -> Result { + let msg = Message::load_from_db(context, msg_id).await?; + let chat = Chat::load_from_db(context, msg.chat_id).await?; ensure!( !chat.id.is_special(), "Message can not belong to a special chat" @@ -1080,8 +1111,8 @@ pub fn create_by_msg_id(context: &Context, msg_id: MsgId) -> Result Result { - let chat_id = match lookup_by_contact_id(context, contact_id) { +pub async fn create_by_contact_id(context: &Context, contact_id: u32) -> Result { + let chat_id = match lookup_by_contact_id(context, contact_id).await { Ok((chat_id, chat_blocked)) => { if chat_blocked != Blocked::Not { // unblock chat (typically move it from the deaddrop to view @@ -1090,7 +1121,8 @@ pub fn create_by_contact_id(context: &Context, contact_id: u32) -> Result { - if !Contact::real_exists_by_id(context, contact_id) && contact_id != DC_CONTACT_ID_SELF + if !Contact::real_exists_by_id(context, contact_id).await + && contact_id != DC_CONTACT_ID_SELF { warn!( context, @@ -1099,7 +1131,7 @@ pub fn create_by_contact_id(context: &Context, contact_id: u32) -> Result Result Result<(), Error> { +pub(crate) async fn update_saved_messages_icon(context: &Context) -> Result<(), Error> { // if there is no saved-messages chat, there is nothing to update. this is no error. - if let Ok((chat_id, _)) = lookup_by_contact_id(context, DC_CONTACT_ID_SELF) { + if let Ok((chat_id, _)) = lookup_by_contact_id(context, DC_CONTACT_ID_SELF).await { let icon = include_bytes!("../assets/icon-saved-messages.png"); let blob = BlobObject::create(context, "icon-saved-messages.png".to_string(), icon)?; let icon = blob.as_name().to_string(); - let mut chat = Chat::load_from_db(context, chat_id)?; + let mut chat = Chat::load_from_db(context, chat_id).await?; chat.param.set(Param::ProfileImage, icon); - chat.update_param(context)?; + chat.update_param(context).await?; } Ok(()) } -pub(crate) fn update_device_icon(context: &Context) -> Result<(), Error> { +pub(crate) async fn update_device_icon(context: &Context) -> Result<(), Error> { // if there is no device-chat, there is nothing to update. this is no error. - if let Ok((chat_id, _)) = lookup_by_contact_id(context, DC_CONTACT_ID_DEVICE) { + if let Ok((chat_id, _)) = lookup_by_contact_id(context, DC_CONTACT_ID_DEVICE).await { let icon = include_bytes!("../assets/icon-device.png"); let blob = BlobObject::create(context, "icon-device.png".to_string(), icon)?; let icon = blob.as_name().to_string(); - let mut chat = Chat::load_from_db(context, chat_id)?; + let mut chat = Chat::load_from_db(context, chat_id).await?; chat.param.set(Param::ProfileImage, &icon); - chat.update_param(context)?; + chat.update_param(context).await?; - let mut contact = Contact::load_from_db(context, DC_CONTACT_ID_DEVICE)?; + let mut contact = Contact::load_from_db(context, DC_CONTACT_ID_DEVICE).await?; contact.param.set(Param::ProfileImage, icon); - contact.update_param(context)?; + contact.update_param(context).await?; } Ok(()) } -fn update_special_chat_name( +async fn update_special_chat_name( context: &Context, contact_id: u32, stock_id: StockMessage, ) -> Result<(), Error> { - if let Ok((chat_id, _)) = lookup_by_contact_id(context, contact_id) { + if let Ok((chat_id, _)) = lookup_by_contact_id(context, contact_id).await { let name: String = context.stock_str(stock_id).into(); // the `!= name` condition avoids unneeded writes - context.sql.execute( - "UPDATE chats SET name=? WHERE id=? AND name!=?;", - params![name, chat_id, name], - )?; + context + .sql + .execute( + "UPDATE chats SET name=? WHERE id=? AND name!=?;", + params![name, chat_id, name], + ) + .await?; } Ok(()) } -pub(crate) fn update_special_chat_names(context: &Context) -> Result<(), Error> { - update_special_chat_name(context, DC_CONTACT_ID_DEVICE, StockMessage::DeviceMessages)?; - update_special_chat_name(context, DC_CONTACT_ID_SELF, StockMessage::SavedMessages)?; +pub(crate) async fn update_special_chat_names(context: &Context) -> Result<(), Error> { + update_special_chat_name(context, DC_CONTACT_ID_DEVICE, StockMessage::DeviceMessages).await?; + update_special_chat_name(context, DC_CONTACT_ID_SELF, StockMessage::SavedMessages).await?; Ok(()) } -pub(crate) fn create_or_lookup_by_contact_id( +pub(crate) async fn create_or_lookup_by_contact_id( context: &Context, contact_id: u32, create_blocked: Blocked, ) -> Result<(ChatId, Blocked), Error> { - ensure!(context.sql.is_open(), "Database not available"); + ensure!(context.sql.is_open().await, "Database not available"); ensure!(contact_id > 0, "Invalid contact id requested"); - if let Ok((chat_id, chat_blocked)) = lookup_by_contact_id(context, contact_id) { + if let Ok((chat_id, chat_blocked)) = lookup_by_contact_id(context, contact_id).await { // Already exists, no need to create. return Ok((chat_id, chat_blocked)); } - let contact = Contact::load_from_db(context, contact_id)?; + let contact = Contact::load_from_db(context, contact_id).await?; let chat_name = contact.get_display_name(); context .sql - .start_stmt("create_or_lookup_by_contact_id transaction"); - context.sql.with_conn(|conn| { - let tx = conn.transaction()?; - tx.execute( - "INSERT INTO chats (type, name, param, blocked, created_timestamp) VALUES(?, ?, ?, ?, ?)", - params![ - Chattype::Single, - chat_name, - match contact_id { - DC_CONTACT_ID_SELF => "K=1".to_string(), // K = Param::Selftalk - DC_CONTACT_ID_DEVICE => "D=1".to_string(), // D = Param::Devicetalk - _ => "".to_string(), - }, - create_blocked as u8, - time(), - ] - )?; - tx.execute( - "INSERT INTO chats_contacts (chat_id, contact_id) VALUES((SELECT last_insert_rowid()), ?)", - params![contact_id])?; - tx.commit()?; - Ok(()) - })?; + .with_conn(|mut conn| { + let conn2 = &mut conn; + let tx = conn2.transaction()?; + tx.execute( + "INSERT INTO chats (type, name, param, blocked, created_timestamp) VALUES(?, ?, ?, ?, ?)", + params![ + Chattype::Single, + chat_name, + match contact_id { + DC_CONTACT_ID_SELF => "K=1".to_string(), // K = Param::Selftalk + DC_CONTACT_ID_DEVICE => "D=1".to_string(), // D = Param::Devicetalk + _ => "".to_string(), + }, + create_blocked as u8, + time(), + ], + )?; + + tx.execute( + "INSERT INTO chats_contacts (chat_id, contact_id) VALUES((SELECT last_insert_rowid()), ?)", + params![contact_id], + )?; + + tx.commit()?; + Ok(()) + }) + .await?; if contact_id == DC_CONTACT_ID_SELF { - update_saved_messages_icon(context)?; + update_saved_messages_icon(context).await?; } else if contact_id == DC_CONTACT_ID_DEVICE { - update_device_icon(context)?; + update_device_icon(context).await?; } - lookup_by_contact_id(context, contact_id) + lookup_by_contact_id(context, contact_id).await } -pub(crate) fn lookup_by_contact_id( +pub(crate) async fn lookup_by_contact_id( context: &Context, contact_id: u32, ) -> Result<(ChatId, Blocked), Error> { - ensure!(context.sql.is_open(), "Database not available"); + ensure!(context.sql.is_open().await, "Database not available"); context .sql @@ -1243,17 +1282,18 @@ pub(crate) fn lookup_by_contact_id( )) }, ) + .await .map_err(Into::into) } -pub fn get_by_contact_id(context: &Context, contact_id: u32) -> Result { - let (chat_id, blocked) = lookup_by_contact_id(context, contact_id)?; +pub async fn get_by_contact_id(context: &Context, contact_id: u32) -> Result { + let (chat_id, blocked) = lookup_by_contact_id(context, contact_id).await?; ensure_eq!(blocked, Blocked::Not, "Requested contact is blocked"); Ok(chat_id) } -pub fn prepare_msg<'a>( +pub async fn prepare_msg<'a>( context: &'a Context, chat_id: ChatId, msg: &mut Message, @@ -1264,7 +1304,7 @@ pub fn prepare_msg<'a>( ); msg.state = MessageState::OutPreparing; - let msg_id = prepare_msg_common(context, chat_id, msg)?; + let msg_id = prepare_msg_common(context, chat_id, msg).await?; context.call_cb(Event::MsgsChanged { chat_id: msg.chat_id, msg_id: msg.id, @@ -1329,16 +1369,16 @@ fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<(), Error> { Ok(()) } -fn prepare_msg_common( +async fn prepare_msg_common( context: &Context, chat_id: ChatId, msg: &mut Message, ) -> Result { msg.id = MsgId::new_unset(); prepare_msg_blob(context, msg)?; - chat_id.unarchive(context)?; + chat_id.unarchive(context).await?; - let mut chat = Chat::load_from_db(context, chat_id)?; + let mut chat = Chat::load_from_db(context, chat_id).await?; ensure!(chat.can_send(), "cannot send to {}", chat_id); // The OutPreparing state is set by dc_prepare_msg() before it @@ -1349,16 +1389,20 @@ fn prepare_msg_common( msg.state = MessageState::OutPending; } - msg.id = chat.prepare_msg_raw(context, msg, dc_create_smeared_timestamp(context))?; + msg.id = chat + .prepare_msg_raw(context, msg, dc_create_smeared_timestamp(context)) + .await?; msg.chat_id = chat_id; Ok(msg.id) } /// Returns whether a contact is in a chat or not. -pub fn is_contact_in_chat(context: &Context, chat_id: ChatId, contact_id: u32) -> bool { - /* this function works for group and for normal chats, however, it is more useful for group chats. - DC_CONTACT_ID_SELF may be used to check, if the user itself is in a group chat (DC_CONTACT_ID_SELF is not added to normal chats) */ +pub async fn is_contact_in_chat(context: &Context, chat_id: ChatId, contact_id: u32) -> bool { + // this function works for group and for normal chats, however, it is more useful + // for group chats. + // DC_CONTACT_ID_SELF may be used to check, if the user itself is in a group + // chat (DC_CONTACT_ID_SELF is not added to normal chats) context .sql @@ -1366,6 +1410,7 @@ pub fn is_contact_in_chat(context: &Context, chat_id: ChatId, contact_id: u32) - "SELECT contact_id FROM chats_contacts WHERE chat_id=? AND contact_id=?;", params![chat_id, contact_id as i32], ) + .await .unwrap_or_default() } @@ -1392,7 +1437,7 @@ pub async fn send_msg( .map_err(|_| InvalidMsgId) .map(MsgId::new) { - if let Ok(mut msg) = Message::load_from_db(context, msg_id) { + if let Ok(mut msg) = Message::load_from_db(context, msg_id).await { send_msg_inner(context, chat_id, &mut msg).await?; }; } @@ -1417,7 +1462,7 @@ async fn send_msg_inner( // the state to OutPending. if msg.state != MessageState::OutPreparing { // automatically prepare normal messages - prepare_msg_common(context, chat_id, msg)?; + prepare_msg_common(context, chat_id, msg).await?; } else { // update message state of separately prepared messages ensure!( @@ -1456,7 +1501,7 @@ pub async fn send_text_msg( send_msg(context, chat_id, &mut msg).await } -pub fn get_chat_msgs( +pub async fn get_chat_msgs( context: &Context, chat_id: ChatId, flags: u32, @@ -1488,10 +1533,12 @@ pub fn get_chat_msgs( Ok(ret) }; let success = if chat_id.is_deaddrop() { - let show_emails = - ShowEmails::from_i32(context.get_config_int(Config::ShowEmails)).unwrap_or_default(); - context.sql.query_map( - "SELECT m.id AS id, m.timestamp AS timestamp + let show_emails = ShowEmails::from_i32(context.get_config_int(Config::ShowEmails).await) + .unwrap_or_default(); + context + .sql + .query_map( + "SELECT m.id AS id, m.timestamp AS timestamp FROM msgs m LEFT JOIN chats ON m.chat_id=chats.id @@ -1504,13 +1551,16 @@ pub fn get_chat_msgs( AND contacts.blocked=0 AND m.msgrmsg>=? ORDER BY m.timestamp,m.id;", - params![if show_emails == ShowEmails::All { 0 } else { 1 }], - process_row, - process_rows, - ) + params![if show_emails == ShowEmails::All { 0 } else { 1 }], + process_row, + process_rows, + ) + .await } else if chat_id.is_starred() { - context.sql.query_map( - "SELECT m.id AS id, m.timestamp AS timestamp + context + .sql + .query_map( + "SELECT m.id AS id, m.timestamp AS timestamp FROM msgs m LEFT JOIN contacts ct ON m.from_id=ct.id @@ -1518,21 +1568,25 @@ pub fn get_chat_msgs( AND m.hidden=0 AND ct.blocked=0 ORDER BY m.timestamp,m.id;", - params![], - process_row, - process_rows, - ) + params![], + process_row, + process_rows, + ) + .await } else { - context.sql.query_map( - "SELECT m.id AS id, m.timestamp AS timestamp + context + .sql + .query_map( + "SELECT m.id AS id, m.timestamp AS timestamp FROM msgs m WHERE m.chat_id=? AND m.hidden=0 ORDER BY m.timestamp, m.id;", - params![chat_id], - process_row, - process_rows, - ) + params![chat_id], + process_row, + process_rows, + ) + .await }; match success { Ok(ret) => ret, @@ -1543,23 +1597,28 @@ pub fn get_chat_msgs( } } -pub fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<(), Error> { - if !context.sql.exists( - "SELECT id FROM msgs WHERE chat_id=? AND state=?;", - params![chat_id, MessageState::InFresh], - )? { +pub async fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<(), Error> { + if !context + .sql + .exists( + "SELECT id FROM msgs WHERE chat_id=? AND state=?;", + params![chat_id, MessageState::InFresh], + ) + .await? + { return Ok(()); } - sql::execute( - context, - &context.sql, - "UPDATE msgs + context + .sql + .execute( + "UPDATE msgs SET state=13 WHERE chat_id=? AND state=10;", - params![chat_id], - )?; + params![chat_id], + ) + .await?; context.call_cb(Event::MsgsChanged { chat_id: ChatId::new(0), @@ -1569,24 +1628,29 @@ pub fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<(), Error> Ok(()) } -pub fn marknoticed_all_chats(context: &Context) -> Result<(), Error> { - if !context.sql.exists( - "SELECT id +pub async fn marknoticed_all_chats(context: &Context) -> Result<(), Error> { + if !context + .sql + .exists( + "SELECT id FROM msgs WHERE state=10;", - params![], - )? { + params![], + ) + .await? + { return Ok(()); } - sql::execute( - context, - &context.sql, - "UPDATE msgs + context + .sql + .execute( + "UPDATE msgs SET state=13 WHERE state=10;", - params![], - )?; + params![], + ) + .await?; context.call_cb(Event::MsgsChanged { msg_id: MsgId::new(0), @@ -1596,7 +1660,7 @@ pub fn marknoticed_all_chats(context: &Context) -> Result<(), Error> { Ok(()) } -pub fn get_chat_media( +pub async fn get_chat_media( context: &Context, chat_id: ChatId, msg_type: Viewtype, @@ -1637,6 +1701,7 @@ pub fn get_chat_media( Ok(ret) }, ) + .await .unwrap_or_default() } @@ -1648,7 +1713,7 @@ pub enum Direction { Backward = -1, } -pub fn get_next_media( +pub async fn get_next_media( context: &Context, curr_msg_id: MsgId, direction: Direction, @@ -1658,7 +1723,7 @@ pub fn get_next_media( ) -> Option { let mut ret: Option = None; - if let Ok(msg) = Message::load_from_db(context, curr_msg_id) { + if let Ok(msg) = Message::load_from_db(context, curr_msg_id).await { let list: Vec = get_chat_media( context, msg.chat_id, @@ -1669,7 +1734,8 @@ pub fn get_next_media( }, msg_type2, msg_type3, - ); + ) + .await; for i in 0..list.len() { if curr_msg_id == list[i] { match direction { @@ -1691,7 +1757,7 @@ pub fn get_next_media( ret } -pub fn get_chat_contacts(context: &Context, chat_id: ChatId) -> Vec { +pub async fn get_chat_contacts(context: &Context, chat_id: ChatId) -> Vec { /* Normal chats do not include SELF. Group chats do (as it may happen that one is deleted from a groupchat but the chats stays visible, moreover, this makes displaying lists easier) */ @@ -1715,10 +1781,11 @@ pub fn get_chat_contacts(context: &Context, chat_id: ChatId) -> Vec { |row| row.get::<_, u32>(0), |ids| ids.collect::, _>>().map_err(Into::into), ) + .await .unwrap_or_default() } -pub fn create_group_chat( +pub async fn create_group_chat( context: &Context, verified: VerifiedStatus, chat_name: impl AsRef, @@ -1728,9 +1795,7 @@ pub fn create_group_chat( let draft_txt = context.stock_string_repl_str(StockMessage::NewGroupDraft, &chat_name); let grpid = dc_create_id(); - sql::execute( - context, - &context.sql, + context.sql.execute( "INSERT INTO chats (type, name, grpid, param, created_timestamp) VALUES(?, ?, ?, \'U=1\', ?);", params![ if verified != VerifiedStatus::Unverified { @@ -1742,12 +1807,15 @@ pub fn create_group_chat( grpid, time(), ], - )?; + ).await?; - let row_id = sql::get_rowid(context, &context.sql, "chats", "grpid", grpid); + let row_id = context + .sql + .get_rowid(context, "chats", "grpid", grpid) + .await?; let chat_id = ChatId::new(row_id); if !chat_id.is_error() { - if add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF) { + if add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF).await { let mut draft_msg = Message::new(Viewtype::Text); draft_msg.set_text(Some(draft_txt)); chat_id.set_draft_raw(context, &mut draft_msg); @@ -1762,36 +1830,38 @@ pub fn create_group_chat( Ok(chat_id) } -pub(crate) fn add_to_chat_contacts_table( +pub(crate) async fn add_to_chat_contacts_table( context: &Context, chat_id: ChatId, contact_id: u32, ) -> bool { // add a contact to a chat; the function does not check the type or if any of the record exist or are already // added to the chat! - sql::execute( - context, - &context.sql, - "INSERT INTO chats_contacts (chat_id, contact_id) VALUES(?, ?)", - params![chat_id, contact_id as i32], - ) - .is_ok() + context + .sql + .execute( + "INSERT INTO chats_contacts (chat_id, contact_id) VALUES(?, ?)", + params![chat_id, contact_id as i32], + ) + .await + .is_ok() } -pub(crate) fn remove_from_chat_contacts_table( +pub(crate) async fn remove_from_chat_contacts_table( context: &Context, chat_id: ChatId, contact_id: u32, ) -> bool { // remove a contact from the chats_contact table unconditionally // the function does not check the type or if the record exist - sql::execute( - context, - &context.sql, - "DELETE FROM chats_contacts WHERE chat_id=? AND contact_id=?", - params![chat_id, contact_id as i32], - ) - .is_ok() + context + .sql + .execute( + "DELETE FROM chats_contacts WHERE chat_id=? AND contact_id=?", + params![chat_id, contact_id as i32], + ) + .await + .is_ok() } /// Adds a contact to the chat. @@ -1812,25 +1882,25 @@ pub(crate) async fn add_contact_to_chat_ex( from_handshake: bool, ) -> Result { ensure!(!chat_id.is_special(), "can not add member to special chats"); - let contact = Contact::get_by_id(context, contact_id)?; + let contact = Contact::get_by_id(context, contact_id).await?; let mut msg = Message::default(); - reset_gossiped_timestamp(context, chat_id)?; + reset_gossiped_timestamp(context, chat_id).await?; /*this also makes sure, not contacts are added to special or normal chats*/ - let mut chat = Chat::load_from_db(context, chat_id)?; + let mut chat = Chat::load_from_db(context, chat_id).await?; ensure!( - real_group_exists(context, chat_id), + real_group_exists(context, chat_id).await, "{} is not a group where one can add members", chat_id ); ensure!( - Contact::real_exists_by_id(context, contact_id) || contact_id == DC_CONTACT_ID_SELF, + Contact::real_exists_by_id(context, contact_id).await || contact_id == DC_CONTACT_ID_SELF, "invalid contact_id {} for adding to group", contact_id ); - if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF as u32) { + if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF as u32).await { /* we should respect this - whatever we send to the group, it gets discarded anyway! */ emit_event!( context, @@ -1840,10 +1910,11 @@ pub(crate) async fn add_contact_to_chat_ex( } if from_handshake && chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 { chat.param.remove(Param::Unpromoted); - chat.update_param(context)?; + chat.update_param(context).await?; } let self_addr = context .get_config(Config::ConfiguredAddr) + .await .unwrap_or_default(); if addr_cmp(contact.get_addr(), &self_addr) { // ourself is added using DC_CONTACT_ID_SELF, do not add this address explicitly. @@ -1855,7 +1926,7 @@ pub(crate) async fn add_contact_to_chat_ex( return Ok(false); } - if is_contact_in_chat(context, chat_id, contact_id) { + if is_contact_in_chat(context, chat_id, contact_id).await { if !from_handshake { return Ok(true); } @@ -1870,7 +1941,7 @@ pub(crate) async fn add_contact_to_chat_ex( ); return Ok(false); } - if !add_to_chat_contacts_table(context, chat_id, contact_id) { + if !add_to_chat_contacts_table(context, chat_id, contact_id).await { return Ok(false); } } @@ -1899,9 +1970,9 @@ pub(crate) async fn add_contact_to_chat_ex( Ok(true) } -fn real_group_exists(context: &Context, chat_id: ChatId) -> bool { +async fn real_group_exists(context: &Context, chat_id: ChatId) -> bool { // check if a group or a verified group exists under the given ID - if !context.sql.is_open() || chat_id.is_special() { + if !context.sql.is_open().await || chat_id.is_special() { return false; } @@ -1911,16 +1982,20 @@ fn real_group_exists(context: &Context, chat_id: ChatId) -> bool { "SELECT id FROM chats WHERE id=? AND (type=120 OR type=130);", params![chat_id], ) + .await .unwrap_or_default() } -pub(crate) fn reset_gossiped_timestamp(context: &Context, chat_id: ChatId) -> Result<(), Error> { - set_gossiped_timestamp(context, chat_id, 0) +pub(crate) async fn reset_gossiped_timestamp( + context: &Context, + chat_id: ChatId, +) -> Result<(), Error> { + set_gossiped_timestamp(context, chat_id, 0).await } /// Get timestamp of the last gossip sent in the chat. /// Zero return value means that gossip was never sent. -pub fn get_gossiped_timestamp(context: &Context, chat_id: ChatId) -> i64 { +pub async fn get_gossiped_timestamp(context: &Context, chat_id: ChatId) -> i64 { context .sql .query_get_value::<_, i64>( @@ -1928,10 +2003,11 @@ pub fn get_gossiped_timestamp(context: &Context, chat_id: ChatId) -> i64 { "SELECT gossiped_timestamp FROM chats WHERE id=?;", params![chat_id], ) + .await .unwrap_or_default() } -pub(crate) fn set_gossiped_timestamp( +pub(crate) async fn set_gossiped_timestamp( context: &Context, chat_id: ChatId, timestamp: i64, @@ -1942,47 +2018,56 @@ pub(crate) fn set_gossiped_timestamp( "set gossiped_timestamp for chat #{} to {}.", chat_id, timestamp, ); - sql::execute( - context, - &context.sql, - "UPDATE chats SET gossiped_timestamp=? WHERE id=?;", - params![timestamp, chat_id], - )?; + context + .sql + .execute( + "UPDATE chats SET gossiped_timestamp=? WHERE id=?;", + params![timestamp, chat_id], + ) + .await?; + Ok(()) } -pub(crate) fn shall_attach_selfavatar(context: &Context, chat_id: ChatId) -> Result { +pub(crate) async fn shall_attach_selfavatar( + context: &Context, + chat_id: ChatId, +) -> Result { // versions before 12/2019 already allowed to set selfavatar, however, it was never sent to others. // to avoid sending out previously set selfavatars unexpectedly we added this additional check. // it can be removed after some time. if !context .sql .get_raw_config_bool(context, "attach_selfavatar") + .await { return Ok(false); } let timestamp_some_days_ago = time() - DC_RESEND_USER_AVATAR_DAYS * 24 * 60 * 60; - let needs_attach = context.sql.query_map( - "SELECT c.selfavatar_sent + let needs_attach = context + .sql + .query_map( + "SELECT c.selfavatar_sent FROM chats_contacts cc LEFT JOIN contacts c ON c.id=cc.contact_id WHERE cc.chat_id=? AND cc.contact_id!=?;", - params![chat_id, DC_CONTACT_ID_SELF], - |row| Ok(row.get::<_, i64>(0)), - |rows| { - let mut needs_attach = false; - for row in rows { - if let Ok(selfavatar_sent) = row { - let selfavatar_sent = selfavatar_sent?; - if selfavatar_sent < timestamp_some_days_ago { - needs_attach = true; + params![chat_id, DC_CONTACT_ID_SELF], + |row| Ok(row.get::<_, i64>(0)), + |rows| { + let mut needs_attach = false; + for row in rows { + if let Ok(selfavatar_sent) = row { + let selfavatar_sent = selfavatar_sent?; + if selfavatar_sent < timestamp_some_days_ago { + needs_attach = true; + } } } - } - Ok(needs_attach) - }, - )?; + Ok(needs_attach) + }, + ) + .await?; Ok(needs_attach) } @@ -2028,16 +2113,21 @@ impl rusqlite::types::FromSql for MuteDuration { } } -pub fn set_muted(context: &Context, chat_id: ChatId, duration: MuteDuration) -> Result<(), Error> { +pub async fn set_muted( + context: &Context, + chat_id: ChatId, + duration: MuteDuration, +) -> Result<(), Error> { ensure!(!chat_id.is_special(), "Invalid chat ID"); - if real_group_exists(context, chat_id) - && sql::execute( - context, - &context.sql, - "UPDATE chats SET muted_until=? WHERE id=?;", - params![duration, chat_id], - ) - .is_ok() + if real_group_exists(context, chat_id).await + && context + .sql + .execute( + "UPDATE chats SET muted_until=? WHERE id=?;", + params![duration, chat_id], + ) + .await + .is_ok() { context.call_cb(Event::ChatModified(chat_id)); } else { @@ -2066,9 +2156,9 @@ pub async fn remove_contact_from_chat( /* we do not check if "contact_id" exists but just delete all records with the id from chats_contacts */ /* this allows to delete pending references to deleted contacts. Of course, this should _not_ happen. */ - if let Ok(chat) = Chat::load_from_db(context, chat_id) { - if real_group_exists(context, chat_id) { - if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF) { + if let Ok(chat) = Chat::load_from_db(context, chat_id).await { + if real_group_exists(context, chat_id).await { + if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF).await { emit_event!( context, Event::ErrorSelfNotInGroup( @@ -2077,11 +2167,11 @@ pub async fn remove_contact_from_chat( ); } else { /* we should respect this - whatever we send to the group, it gets discarded anyway! */ - if let Ok(contact) = Contact::get_by_id(context, contact_id) { + if let Ok(contact) = Contact::get_by_id(context, contact_id).await { if chat.is_promoted() { msg.viewtype = Viewtype::Text; if contact.id == DC_CONTACT_ID_SELF { - set_group_explicitly_left(context, chat.grpid)?; + set_group_explicitly_left(context, chat.grpid).await?; msg.text = Some(context.stock_system_msg( StockMessage::MsgGroupLeft, "", @@ -2105,7 +2195,7 @@ pub async fn remove_contact_from_chat( }); } } - if remove_from_chat_contacts_table(context, chat_id, contact_id) { + if remove_from_chat_contacts_table(context, chat_id, contact_id).await { context.call_cb(Event::ChatModified(chat_id)); success = true; } @@ -2120,20 +2210,21 @@ pub async fn remove_contact_from_chat( Ok(()) } -fn set_group_explicitly_left(context: &Context, grpid: impl AsRef) -> Result<(), Error> { - if !is_group_explicitly_left(context, grpid.as_ref())? { - sql::execute( - context, - &context.sql, - "INSERT INTO leftgrps (grpid) VALUES(?);", - params![grpid.as_ref()], - )?; +async fn set_group_explicitly_left(context: &Context, grpid: impl AsRef) -> Result<(), Error> { + if !is_group_explicitly_left(context, grpid.as_ref()).await? { + context + .sql + .execute( + "INSERT INTO leftgrps (grpid) VALUES(?);", + params![grpid.as_ref()], + ) + .await?; } Ok(()) } -pub(crate) fn is_group_explicitly_left( +pub(crate) async fn is_group_explicitly_left( context: &Context, grpid: impl AsRef, ) -> Result { @@ -2143,6 +2234,7 @@ pub(crate) fn is_group_explicitly_left( "SELECT id FROM leftgrps WHERE grpid=?;", params![grpid.as_ref()], ) + .await .map_err(Into::into) } @@ -2157,26 +2249,27 @@ pub async fn set_chat_name( ensure!(!new_name.as_ref().is_empty(), "Invalid name"); ensure!(!chat_id.is_special(), "Invalid chat ID"); - let chat = Chat::load_from_db(context, chat_id)?; + let chat = Chat::load_from_db(context, chat_id).await?; let mut msg = Message::default(); - if real_group_exists(context, chat_id) { + if real_group_exists(context, chat_id).await { if chat.name == new_name.as_ref() { success = true; - } else if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF) { + } else if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF).await { emit_event!( context, Event::ErrorSelfNotInGroup("Cannot set chat name; self not in group".into()) ); } else { /* we should respect this - whatever we send to the group, it gets discarded anyway! */ - if sql::execute( - context, - &context.sql, - "UPDATE chats SET name=? WHERE id=?;", - params![new_name.as_ref(), chat_id], - ) - .is_ok() + if context + .sql + .execute( + "UPDATE chats SET name=? WHERE id=?;", + params![new_name.as_ref(), chat_id], + ) + .await + .is_ok() { if chat.is_promoted() { msg.viewtype = Viewtype::Text; @@ -2220,13 +2313,13 @@ pub async fn set_chat_profile_image( new_image: impl AsRef, // XXX use PathBuf ) -> Result<(), Error> { ensure!(!chat_id.is_special(), "Invalid chat ID"); - let mut chat = Chat::load_from_db(context, chat_id)?; + let mut chat = Chat::load_from_db(context, chat_id).await?; ensure!( - real_group_exists(context, chat_id), + real_group_exists(context, chat_id).await, "Failed to set profile image; group does not exist" ); /* we should respect this - whatever we send to the group, it gets discarded anyway! */ - if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF) { + if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF).await { emit_event!( context, Event::ErrorSelfNotInGroup("Cannot set chat profile image; self not in group.".into()) @@ -2264,7 +2357,7 @@ pub async fn set_chat_profile_image( DC_CONTACT_ID_SELF, )); } - chat.update_param(context)?; + chat.update_param(context).await?; if chat.is_promoted() { msg.id = send_msg(context, chat_id, &mut msg).await?; emit_event!( @@ -2291,23 +2384,26 @@ pub async fn forward_msgs( let mut created_msgs: Vec = Vec::new(); let mut curr_timestamp: i64; - chat_id.unarchive(context)?; - if let Ok(mut chat) = Chat::load_from_db(context, chat_id) { + chat_id.unarchive(context).await?; + if let Ok(mut chat) = Chat::load_from_db(context, chat_id).await { ensure!(chat.can_send(), "cannot send to {}", chat_id); curr_timestamp = dc_create_smeared_timestamps(context, msg_ids.len()); - let ids = context.sql.query_map( - format!( - "SELECT id FROM msgs WHERE id IN({}) ORDER BY timestamp,id", - msg_ids.iter().map(|_| "?").join(",") - ), - msg_ids, - |row| row.get::<_, MsgId>(0), - |ids| ids.collect::, _>>().map_err(Into::into), - )?; + let ids = context + .sql + .query_map( + format!( + "SELECT id FROM msgs WHERE id IN({}) ORDER BY timestamp,id", + msg_ids.iter().map(|_| "?").join(",") + ), + msg_ids, + |row| row.get::<_, MsgId>(0), + |ids| ids.collect::, _>>().map_err(Into::into), + ) + .await?; for id in ids { let src_msg_id: MsgId = id; - let msg = Message::load_from_db(context, src_msg_id); + let msg = Message::load_from_db(context, src_msg_id).await; if msg.is_err() { break; } @@ -2327,7 +2423,7 @@ pub async fn forward_msgs( if msg.state == MessageState::OutPreparing { let fresh9 = curr_timestamp; curr_timestamp += 1; - new_msg_id = chat.prepare_msg_raw(context, &mut msg, fresh9)?; + new_msg_id = chat.prepare_msg_raw(context, &mut msg, fresh9).await?; let save_param = msg.param.clone(); msg.param = original_param; msg.id = src_msg_id; @@ -2346,7 +2442,7 @@ pub async fn forward_msgs( msg.state = MessageState::OutPending; let fresh10 = curr_timestamp; curr_timestamp += 1; - new_msg_id = chat.prepare_msg_raw(context, &mut msg, fresh10)?; + new_msg_id = chat.prepare_msg_raw(context, &mut msg, fresh10).await?; job::send_msg(context, new_msg_id).await?; } created_chats.push(chat_id); @@ -2362,7 +2458,7 @@ pub async fn forward_msgs( Ok(()) } -pub(crate) fn get_chat_contact_cnt(context: &Context, chat_id: ChatId) -> usize { +pub(crate) async fn get_chat_contact_cnt(context: &Context, chat_id: ChatId) -> usize { context .sql .query_get_value::<_, isize>( @@ -2370,11 +2466,12 @@ pub(crate) fn get_chat_contact_cnt(context: &Context, chat_id: ChatId) -> usize "SELECT COUNT(*) FROM chats_contacts WHERE chat_id=?;", params![chat_id], ) + .await .unwrap_or_default() as usize } -pub(crate) fn get_chat_cnt(context: &Context) -> usize { - if context.sql.is_open() { +pub(crate) async fn get_chat_cnt(context: &Context) -> usize { + if context.sql.is_open().await { /* no database, no chats - this is no error (needed eg. for information) */ context .sql @@ -2383,33 +2480,37 @@ pub(crate) fn get_chat_cnt(context: &Context) -> usize { "SELECT COUNT(*) FROM chats WHERE id>9 AND blocked=0;", params![], ) + .await .unwrap_or_default() as usize } else { 0 } } -pub(crate) fn get_chat_id_by_grpid( +pub(crate) async fn get_chat_id_by_grpid( context: &Context, grpid: impl AsRef, ) -> Result<(ChatId, bool, Blocked), sql::Error> { - context.sql.query_row( - "SELECT id, blocked, type FROM chats WHERE grpid=?;", - params![grpid.as_ref()], - |row| { - let chat_id = row.get::<_, ChatId>(0)?; + context + .sql + .query_row( + "SELECT id, blocked, type FROM chats WHERE grpid=?;", + params![grpid.as_ref()], + |row| { + let chat_id = row.get::<_, ChatId>(0)?; - let b = row.get::<_, Option>(1)?.unwrap_or_default(); - let v = row.get::<_, Option>(2)?.unwrap_or_default(); - Ok((chat_id, v == Chattype::VerifiedGroup, b)) - }, - ) + let b = row.get::<_, Option>(1)?.unwrap_or_default(); + let v = row.get::<_, Option>(2)?.unwrap_or_default(); + Ok((chat_id, v == Chattype::VerifiedGroup, b)) + }, + ) + .await } /// Adds a message to device chat. /// /// Optional `label` can be provided to ensure that message is added only once. -pub fn add_device_msg( +pub async fn add_device_msg( context: &Context, label: Option<&str>, msg: Option<&mut Message>, @@ -2422,19 +2523,21 @@ pub fn add_device_msg( let mut msg_id = MsgId::new_unset(); if let Some(label) = label { - if was_device_msg_ever_added(context, label)? { + if was_device_msg_ever_added(context, label).await? { info!(context, "device-message {} already added", label); return Ok(msg_id); } } if let Some(msg) = msg { - chat_id = create_or_lookup_by_contact_id(context, DC_CONTACT_ID_DEVICE, Blocked::Not)?.0; + chat_id = create_or_lookup_by_contact_id(context, DC_CONTACT_ID_DEVICE, Blocked::Not) + .await? + .0; let rfc724_mid = dc_create_outgoing_rfc724_mid(None, "@device"); msg.try_calc_and_set_dimensions(context).ok(); prepare_msg_blob(context, msg)?; - chat_id.unarchive(context)?; + chat_id.unarchive(context).await?; context.sql.execute( "INSERT INTO msgs (chat_id,from_id,to_id, timestamp,type,state, txt,param,rfc724_mid) \ @@ -2450,17 +2553,23 @@ pub fn add_device_msg( msg.param.to_string(), rfc724_mid, ], - )?; + ).await?; - let row_id = sql::get_rowid(context, &context.sql, "msgs", "rfc724_mid", &rfc724_mid); + let row_id = context + .sql + .get_rowid(context, "msgs", "rfc724_mid", &rfc724_mid) + .await?; msg_id = MsgId::new(row_id); } if let Some(label) = label { - context.sql.execute( - "INSERT INTO devmsglabels (label) VALUES (?);", - params![label], - )?; + context + .sql + .execute( + "INSERT INTO devmsglabels (label) VALUES (?);", + params![label], + ) + .await?; } if !msg_id.is_unset() { @@ -2470,13 +2579,17 @@ pub fn add_device_msg( Ok(msg_id) } -pub fn was_device_msg_ever_added(context: &Context, label: &str) -> Result { +pub async fn was_device_msg_ever_added(context: &Context, label: &str) -> Result { ensure!(!label.is_empty(), "empty label"); - if let Ok(()) = context.sql.query_row( - "SELECT label FROM devmsglabels WHERE label=?", - params![label], - |_| Ok(()), - ) { + if let Ok(()) = context + .sql + .query_row( + "SELECT label FROM devmsglabels WHERE label=?", + params![label], + |_| Ok(()), + ) + .await + { return Ok(true); } @@ -2488,21 +2601,25 @@ pub fn was_device_msg_ever_added(context: &Context, label: &str) -> Result Result<(), Error> { - context.sql.execute( - "DELETE FROM msgs WHERE from_id=?;", - params![DC_CONTACT_ID_DEVICE], - )?; +pub(crate) async fn delete_and_reset_all_device_msgs(context: &Context) -> Result<(), Error> { context .sql - .execute("DELETE FROM devmsglabels;", params![])?; + .execute( + "DELETE FROM msgs WHERE from_id=?;", + params![DC_CONTACT_ID_DEVICE], + ) + .await?; + context + .sql + .execute("DELETE FROM devmsglabels;", params![]) + .await?; Ok(()) } /// Adds an informational message to chat. /// /// For example, it can be a message showing that a member was added to a group. -pub(crate) fn add_info_msg(context: &Context, chat_id: ChatId, text: impl AsRef) { +pub(crate) async fn add_info_msg(context: &Context, chat_id: ChatId, text: impl AsRef) { let rfc724_mid = dc_create_outgoing_rfc724_mid(None, "@device"); if context.sql.execute( @@ -2517,11 +2634,15 @@ pub(crate) fn add_info_msg(context: &Context, chat_id: ChatId, text: impl AsRef< text.as_ref(), rfc724_mid, ] - ).is_err() { + ).await.is_err() { return; } - let row_id = sql::get_rowid(context, &context.sql, "msgs", "rfc724_mid", &rfc724_mid); + let row_id = context + .sql + .get_rowid(context, "msgs", "rfc724_mid", &rfc724_mid) + .await + .unwrap_or_default(); context.call_cb(Event::MsgsChanged { chat_id, msg_id: MsgId::new(row_id), @@ -2535,13 +2656,15 @@ mod tests { use crate::contact::Contact; use crate::test_utils::*; - #[test] - fn test_chat_info() { + #[async_std::test] + async fn test_chat_info() { let t = dummy_context(); - let bob = Contact::create(&t.ctx, "bob", "bob@example.com").unwrap(); - let chat_id = create_by_contact_id(&t.ctx, bob).unwrap(); - let chat = Chat::load_from_db(&t.ctx, chat_id).unwrap(); - let info = chat.get_info(&t.ctx).unwrap(); + let bob = Contact::create(&t.ctx, "bob", "bob@example.com") + .await + .unwrap(); + let chat_id = create_by_contact_id(&t.ctx, bob).await.unwrap(); + let chat = Chat::load_from_db(&t.ctx, chat_id).await.unwrap(); + let info = chat.get_info(&t.ctx).await.unwrap(); // Ensure we can serialize this. println!("{}", serde_json::to_string_pretty(&info).unwrap()); @@ -2568,40 +2691,45 @@ mod tests { assert_eq!(info, loaded); } - #[test] - fn test_get_draft_no_draft() { + #[async_std::test] + async fn test_get_draft_no_draft() { let t = dummy_context(); - let chat_id = create_by_contact_id(&t.ctx, DC_CONTACT_ID_SELF).unwrap(); - let draft = chat_id.get_draft(&t.ctx).unwrap(); + let chat_id = create_by_contact_id(&t.ctx, DC_CONTACT_ID_SELF) + .await + .unwrap(); + let draft = chat_id.get_draft(&t.ctx).await.unwrap(); assert!(draft.is_none()); } - #[test] - fn test_get_draft_special_chat_id() { + #[async_std::test] + async fn test_get_draft_special_chat_id() { let t = dummy_context(); let draft = ChatId::new(DC_CHAT_ID_LAST_SPECIAL) .get_draft(&t.ctx) + .await .unwrap(); assert!(draft.is_none()); } - #[test] - fn test_get_draft_no_chat() { + #[async_std::test] + async fn test_get_draft_no_chat() { // This is a weird case, maybe this should be an error but we // do not get this info from the database currently. let t = dummy_context(); - let draft = ChatId::new(42).get_draft(&t.ctx).unwrap(); + let draft = ChatId::new(42).get_draft(&t.ctx).await.unwrap(); assert!(draft.is_none()); } - #[test] - fn test_get_draft() { + #[async_std::test] + async fn test_get_draft() { let t = dummy_context(); - let chat_id = create_by_contact_id(&t.ctx, DC_CONTACT_ID_SELF).unwrap(); + let chat_id = create_by_contact_id(&t.ctx, DC_CONTACT_ID_SELF) + .await + .unwrap(); let mut msg = Message::new(Viewtype::Text); msg.set_text(Some("hello".to_string())); chat_id.set_draft(&t.ctx, Some(&mut msg)); - let draft = chat_id.get_draft(&t.ctx).unwrap().unwrap(); + let draft = chat_id.get_draft(&t.ctx).await.unwrap().unwrap(); let msg_text = msg.get_text(); let draft_text = draft.get_text(); assert_eq!(msg_text, draft_text); @@ -2611,33 +2739,39 @@ mod tests { async fn test_add_contact_to_chat_ex_add_self() { // Adding self to a contact should succeed, even though it's pointless. let t = test_context(Some(Box::new(logging_cb))); - let chat_id = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo").unwrap(); + let chat_id = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo") + .await + .unwrap(); let added = add_contact_to_chat_ex(&t.ctx, chat_id, DC_CONTACT_ID_SELF, false) .await .unwrap(); assert_eq!(added, false); } - #[test] - fn test_self_talk() { + #[async_std::test] + async fn test_self_talk() { let t = dummy_context(); - let chat_id = create_by_contact_id(&t.ctx, DC_CONTACT_ID_SELF).unwrap(); + let chat_id = create_by_contact_id(&t.ctx, DC_CONTACT_ID_SELF) + .await + .unwrap(); assert_eq!(DC_CONTACT_ID_SELF, 1); assert!(!chat_id.is_special()); - let chat = Chat::load_from_db(&t.ctx, chat_id).unwrap(); + let chat = Chat::load_from_db(&t.ctx, chat_id).await.unwrap(); assert_eq!(chat.id, chat_id); assert!(chat.is_self_talk()); assert!(chat.visibility == ChatVisibility::Normal); assert!(!chat.is_device_talk()); assert!(chat.can_send()); assert_eq!(chat.name, t.ctx.stock_str(StockMessage::SavedMessages)); - assert!(chat.get_profile_image(&t.ctx).is_some()); + assert!(chat.get_profile_image(&t.ctx).await.is_some()); } - #[test] - fn test_deaddrop_chat() { + #[async_std::test] + async fn test_deaddrop_chat() { let t = dummy_context(); - let chat = Chat::load_from_db(&t.ctx, ChatId::new(DC_CHAT_ID_DEADDROP)).unwrap(); + let chat = Chat::load_from_db(&t.ctx, ChatId::new(DC_CHAT_ID_DEADDROP)) + .await + .unwrap(); assert_eq!(DC_CHAT_ID_DEADDROP, 1); assert!(chat.id.is_deaddrop()); assert!(!chat.is_self_talk()); @@ -2647,24 +2781,24 @@ mod tests { assert_eq!(chat.name, t.ctx.stock_str(StockMessage::DeadDrop)); } - #[test] - fn test_add_device_msg_unlabelled() { + #[async_std::test] + async fn test_add_device_msg_unlabelled() { let t = test_context(Some(Box::new(logging_cb))); // add two device-messages let mut msg1 = Message::new(Viewtype::Text); msg1.text = Some("first message".to_string()); - let msg1_id = add_device_msg(&t.ctx, None, Some(&mut msg1)); + let msg1_id = add_device_msg(&t.ctx, None, Some(&mut msg1)).await; assert!(msg1_id.is_ok()); let mut msg2 = Message::new(Viewtype::Text); msg2.text = Some("second message".to_string()); - let msg2_id = add_device_msg(&t.ctx, None, Some(&mut msg2)); + let msg2_id = add_device_msg(&t.ctx, None, Some(&mut msg2)).await; assert!(msg2_id.is_ok()); assert_ne!(msg1_id.as_ref().unwrap(), msg2_id.as_ref().unwrap()); // check added messages - let msg1 = message::Message::load_from_db(&t.ctx, msg1_id.unwrap()); + let msg1 = message::Message::load_from_db(&t.ctx, msg1_id.unwrap()).await; assert!(msg1.is_ok()); let msg1 = msg1.unwrap(); assert_eq!(msg1.text.as_ref().unwrap(), "first message"); @@ -2673,13 +2807,13 @@ mod tests { assert!(!msg1.is_info()); assert!(!msg1.is_setupmessage()); - let msg2 = message::Message::load_from_db(&t.ctx, msg2_id.unwrap()); + let msg2 = message::Message::load_from_db(&t.ctx, msg2_id.unwrap()).await; assert!(msg2.is_ok()); let msg2 = msg2.unwrap(); assert_eq!(msg2.text.as_ref().unwrap(), "second message"); // check device chat - assert_eq!(msg2.chat_id.get_msg_cnt(&t.ctx), 2); + assert_eq!(msg2.chat_id.get_msg_cnt(&t.ctx).await, 2); } #[async_std::test] @@ -2689,18 +2823,18 @@ mod tests { // add two device-messages with the same label (second attempt is not added) let mut msg1 = Message::new(Viewtype::Text); msg1.text = Some("first message".to_string()); - let msg1_id = add_device_msg(&t.ctx, Some("any-label"), Some(&mut msg1)); + let msg1_id = add_device_msg(&t.ctx, Some("any-label"), Some(&mut msg1)).await; assert!(msg1_id.is_ok()); assert!(!msg1_id.as_ref().unwrap().is_unset()); let mut msg2 = Message::new(Viewtype::Text); msg2.text = Some("second message".to_string()); - let msg2_id = add_device_msg(&t.ctx, Some("any-label"), Some(&mut msg2)); + let msg2_id = add_device_msg(&t.ctx, Some("any-label"), Some(&mut msg2)).await; assert!(msg2_id.is_ok()); assert!(msg2_id.as_ref().unwrap().is_unset()); // check added message - let msg1 = message::Message::load_from_db(&t.ctx, *msg1_id.as_ref().unwrap()); + let msg1 = message::Message::load_from_db(&t.ctx, *msg1_id.as_ref().unwrap()).await; assert!(msg1.is_ok()); let msg1 = msg1.unwrap(); assert_eq!(msg1_id.as_ref().unwrap(), &msg1.id); @@ -2712,9 +2846,9 @@ mod tests { // check device chat let chat_id = msg1.chat_id; - assert_eq!(chat_id.get_msg_cnt(&t.ctx), 1); + assert_eq!(chat_id.get_msg_cnt(&t.ctx).await, 1); assert!(!chat_id.is_special()); - let chat = Chat::load_from_db(&t.ctx, chat_id); + let chat = Chat::load_from_db(&t.ctx, chat_id).await; assert!(chat.is_ok()); let chat = chat.unwrap(); assert_eq!(chat.get_type(), Chattype::Single); @@ -2722,51 +2856,59 @@ mod tests { assert!(!chat.is_self_talk()); assert!(!chat.can_send()); assert_eq!(chat.name, t.ctx.stock_str(StockMessage::DeviceMessages)); - assert!(chat.get_profile_image(&t.ctx).is_some()); + assert!(chat.get_profile_image(&t.ctx).await.is_some()); // delete device message, make sure it is not added again message::delete_msgs(&t.ctx, &[*msg1_id.as_ref().unwrap()]).await; - let msg1 = message::Message::load_from_db(&t.ctx, *msg1_id.as_ref().unwrap()); + let msg1 = message::Message::load_from_db(&t.ctx, *msg1_id.as_ref().unwrap()).await; assert!(msg1.is_err() || msg1.unwrap().chat_id.is_trash()); - let msg3_id = add_device_msg(&t.ctx, Some("any-label"), Some(&mut msg2)); + let msg3_id = add_device_msg(&t.ctx, Some("any-label"), Some(&mut msg2)).await; assert!(msg3_id.is_ok()); assert!(msg2_id.as_ref().unwrap().is_unset()); } - #[test] - fn test_add_device_msg_label_only() { + #[async_std::test] + async fn test_add_device_msg_label_only() { let t = test_context(Some(Box::new(logging_cb))); - let res = add_device_msg(&t.ctx, Some(""), None); + let res = add_device_msg(&t.ctx, Some(""), None).await; assert!(res.is_err()); - let res = add_device_msg(&t.ctx, Some("some-label"), None); + let res = add_device_msg(&t.ctx, Some("some-label"), None).await; assert!(res.is_ok()); let mut msg = Message::new(Viewtype::Text); msg.text = Some("message text".to_string()); - let msg_id = add_device_msg(&t.ctx, Some("some-label"), Some(&mut msg)); + let msg_id = add_device_msg(&t.ctx, Some("some-label"), Some(&mut msg)).await; assert!(msg_id.is_ok()); assert!(msg_id.as_ref().unwrap().is_unset()); - let msg_id = add_device_msg(&t.ctx, Some("unused-label"), Some(&mut msg)); + let msg_id = add_device_msg(&t.ctx, Some("unused-label"), Some(&mut msg)).await; assert!(msg_id.is_ok()); assert!(!msg_id.as_ref().unwrap().is_unset()); } - #[test] - fn test_was_device_msg_ever_added() { + #[async_std::test] + async fn test_was_device_msg_ever_added() { let t = test_context(Some(Box::new(logging_cb))); - add_device_msg(&t.ctx, Some("some-label"), None).ok(); - assert!(was_device_msg_ever_added(&t.ctx, "some-label").unwrap()); + add_device_msg(&t.ctx, Some("some-label"), None).await.ok(); + assert!(was_device_msg_ever_added(&t.ctx, "some-label") + .await + .unwrap()); let mut msg = Message::new(Viewtype::Text); msg.text = Some("message text".to_string()); - add_device_msg(&t.ctx, Some("another-label"), Some(&mut msg)).ok(); - assert!(was_device_msg_ever_added(&t.ctx, "another-label").unwrap()); + add_device_msg(&t.ctx, Some("another-label"), Some(&mut msg)) + .await + .ok(); + assert!(was_device_msg_ever_added(&t.ctx, "another-label") + .await + .unwrap()); - assert!(!was_device_msg_ever_added(&t.ctx, "unused-label").unwrap()); + assert!(!was_device_msg_ever_added(&t.ctx, "unused-label") + .await + .unwrap()); - assert!(was_device_msg_ever_added(&t.ctx, "").is_err()); + assert!(was_device_msg_ever_added(&t.ctx, "").await.is_err()); } #[async_std::test] @@ -2775,14 +2917,18 @@ mod tests { let mut msg = Message::new(Viewtype::Text); msg.text = Some("message text".to_string()); - add_device_msg(&t.ctx, Some("some-label"), Some(&mut msg)).ok(); - let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap(); + add_device_msg(&t.ctx, Some("some-label"), Some(&mut msg)) + .await + .ok(); + let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); 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.ctx).await.ok(); - add_device_msg(&t.ctx, Some("some-label"), Some(&mut msg)).ok(); - assert_eq!(chatlist_len(&t.ctx, 0), 0) + add_device_msg(&t.ctx, Some("some-label"), Some(&mut msg)) + .await + .ok(); + assert_eq!(chatlist_len(&t.ctx, 0).await, 0) } #[async_std::test] @@ -2790,136 +2936,165 @@ mod tests { let t = test_context(Some(Box::new(logging_cb))); t.ctx.update_device_chats().unwrap(); let (device_chat_id, _) = - create_or_lookup_by_contact_id(&t.ctx, DC_CONTACT_ID_DEVICE, Blocked::Not).unwrap(); + create_or_lookup_by_contact_id(&t.ctx, DC_CONTACT_ID_DEVICE, Blocked::Not) + .await + .unwrap(); let mut msg = Message::new(Viewtype::Text); msg.text = Some("message text".to_string()); assert!(send_msg(&t.ctx, device_chat_id, &mut msg).await.is_err()); - assert!(prepare_msg(&t.ctx, device_chat_id, &mut msg).is_err()); + assert!(prepare_msg(&t.ctx, device_chat_id, &mut msg).await.is_err()); - let msg_id = add_device_msg(&t.ctx, None, Some(&mut msg)).unwrap(); + let msg_id = add_device_msg(&t.ctx, None, Some(&mut msg)).await.unwrap(); assert!(forward_msgs(&t.ctx, &[msg_id], device_chat_id) .await .is_err()); } - #[test] - fn test_delete_and_reset_all_device_msgs() { + #[async_std::test] + async fn test_delete_and_reset_all_device_msgs() { let t = test_context(Some(Box::new(logging_cb))); let mut msg = Message::new(Viewtype::Text); msg.text = Some("message text".to_string()); - let msg_id1 = add_device_msg(&t.ctx, Some("some-label"), Some(&mut msg)).unwrap(); + let msg_id1 = add_device_msg(&t.ctx, Some("some-label"), Some(&mut msg)) + .await + .unwrap(); // adding a device message with the same label won't be executed again ... - assert!(was_device_msg_ever_added(&t.ctx, "some-label").unwrap()); - let msg_id2 = add_device_msg(&t.ctx, Some("some-label"), Some(&mut msg)).unwrap(); + assert!(was_device_msg_ever_added(&t.ctx, "some-label") + .await + .unwrap()); + let msg_id2 = add_device_msg(&t.ctx, Some("some-label"), Some(&mut msg)) + .await + .unwrap(); assert!(msg_id2.is_unset()); // ... unless everything is deleted and resetted - as needed eg. on device switch - delete_and_reset_all_device_msgs(&t.ctx).unwrap(); - assert!(!was_device_msg_ever_added(&t.ctx, "some-label").unwrap()); - let msg_id3 = add_device_msg(&t.ctx, Some("some-label"), Some(&mut msg)).unwrap(); + delete_and_reset_all_device_msgs(&t.ctx).await.unwrap(); + assert!(!was_device_msg_ever_added(&t.ctx, "some-label") + .await + .unwrap()); + let msg_id3 = add_device_msg(&t.ctx, Some("some-label"), Some(&mut msg)) + .await + .unwrap(); assert_ne!(msg_id1, msg_id3); } - fn chatlist_len(ctx: &Context, listflags: usize) -> usize { + async fn chatlist_len(ctx: &Context, listflags: usize) -> usize { Chatlist::try_load(ctx, listflags, None, None) + .await .unwrap() .len() } - #[test] - fn test_archive() { + #[async_std::test] + async fn test_archive() { // create two chats let t = dummy_context(); let mut msg = Message::new(Viewtype::Text); msg.text = Some("foo".to_string()); - let msg_id = add_device_msg(&t.ctx, None, Some(&mut msg)).unwrap(); + let msg_id = add_device_msg(&t.ctx, None, Some(&mut msg)).await.unwrap(); let chat_id1 = message::Message::load_from_db(&t.ctx, msg_id) + .await .unwrap() .chat_id; - let chat_id2 = create_by_contact_id(&t.ctx, DC_CONTACT_ID_SELF).unwrap(); + let chat_id2 = create_by_contact_id(&t.ctx, DC_CONTACT_ID_SELF) + .await + .unwrap(); assert!(!chat_id1.is_special()); assert!(!chat_id2.is_special()); - assert_eq!(get_chat_cnt(&t.ctx), 2); - assert_eq!(chatlist_len(&t.ctx, 0), 2); - assert_eq!(chatlist_len(&t.ctx, DC_GCL_NO_SPECIALS), 2); - assert_eq!(chatlist_len(&t.ctx, DC_GCL_ARCHIVED_ONLY), 0); + assert_eq!(get_chat_cnt(&t.ctx).await, 2); + assert_eq!(chatlist_len(&t.ctx, 0).await, 2); + assert_eq!(chatlist_len(&t.ctx, DC_GCL_NO_SPECIALS).await, 2); + assert_eq!(chatlist_len(&t.ctx, DC_GCL_ARCHIVED_ONLY).await, 0); assert_eq!(DC_GCL_ARCHIVED_ONLY, 0x01); assert_eq!(DC_GCL_NO_SPECIALS, 0x02); // archive first chat assert!(chat_id1 .set_visibility(&t.ctx, ChatVisibility::Archived) + .await .is_ok()); assert!( Chat::load_from_db(&t.ctx, chat_id1) + .await .unwrap() .get_visibility() == ChatVisibility::Archived ); assert!( Chat::load_from_db(&t.ctx, chat_id2) + .await .unwrap() .get_visibility() == ChatVisibility::Normal ); - assert_eq!(get_chat_cnt(&t.ctx), 2); - assert_eq!(chatlist_len(&t.ctx, 0), 2); // including DC_CHAT_ID_ARCHIVED_LINK now - assert_eq!(chatlist_len(&t.ctx, DC_GCL_NO_SPECIALS), 1); - assert_eq!(chatlist_len(&t.ctx, DC_GCL_ARCHIVED_ONLY), 1); + assert_eq!(get_chat_cnt(&t.ctx).await, 2); + assert_eq!(chatlist_len(&t.ctx, 0).await, 2); // including DC_CHAT_ID_ARCHIVED_LINK now + assert_eq!(chatlist_len(&t.ctx, DC_GCL_NO_SPECIALS).await, 1); + assert_eq!(chatlist_len(&t.ctx, DC_GCL_ARCHIVED_ONLY).await, 1); // archive second chat assert!(chat_id2 .set_visibility(&t.ctx, ChatVisibility::Archived) + .await .is_ok()); assert!( Chat::load_from_db(&t.ctx, chat_id1) + .await .unwrap() .get_visibility() == ChatVisibility::Archived ); assert!( Chat::load_from_db(&t.ctx, chat_id2) + .await .unwrap() .get_visibility() == ChatVisibility::Archived ); - assert_eq!(get_chat_cnt(&t.ctx), 2); - assert_eq!(chatlist_len(&t.ctx, 0), 1); // only DC_CHAT_ID_ARCHIVED_LINK now - assert_eq!(chatlist_len(&t.ctx, DC_GCL_NO_SPECIALS), 0); - assert_eq!(chatlist_len(&t.ctx, DC_GCL_ARCHIVED_ONLY), 2); + assert_eq!(get_chat_cnt(&t.ctx).await, 2); + assert_eq!(chatlist_len(&t.ctx, 0).await, 1); // only DC_CHAT_ID_ARCHIVED_LINK now + assert_eq!(chatlist_len(&t.ctx, DC_GCL_NO_SPECIALS).await, 0); + assert_eq!(chatlist_len(&t.ctx, DC_GCL_ARCHIVED_ONLY).await, 2); // archive already archived first chat, unarchive second chat two times assert!(chat_id1 .set_visibility(&t.ctx, ChatVisibility::Archived) + .await .is_ok()); assert!(chat_id2 .set_visibility(&t.ctx, ChatVisibility::Normal) + .await .is_ok()); assert!(chat_id2 .set_visibility(&t.ctx, ChatVisibility::Normal) + .await .is_ok()); assert!( Chat::load_from_db(&t.ctx, chat_id1) + .await .unwrap() .get_visibility() == ChatVisibility::Archived ); assert!( Chat::load_from_db(&t.ctx, chat_id2) + .await .unwrap() .get_visibility() == ChatVisibility::Normal ); - assert_eq!(get_chat_cnt(&t.ctx), 2); - assert_eq!(chatlist_len(&t.ctx, 0), 2); - assert_eq!(chatlist_len(&t.ctx, DC_GCL_NO_SPECIALS), 1); - assert_eq!(chatlist_len(&t.ctx, DC_GCL_ARCHIVED_ONLY), 1); + assert_eq!(get_chat_cnt(&t.ctx).await, 2); + assert_eq!(chatlist_len(&t.ctx, 0).await, 2); + assert_eq!(chatlist_len(&t.ctx, DC_GCL_NO_SPECIALS).await, 1); + assert_eq!(chatlist_len(&t.ctx, DC_GCL_ARCHIVED_ONLY).await, 1); } - fn get_chats_from_chat_list(ctx: &Context, listflags: usize) -> Vec { - let chatlist = Chatlist::try_load(ctx, listflags, None, None).unwrap(); + async fn get_chats_from_chat_list(ctx: &Context, listflags: usize) -> Vec { + let chatlist = Chatlist::try_load(ctx, listflags, None, None) + .await + .unwrap(); let mut result = Vec::new(); for chatlist_index in 0..chatlist.len() { result.push(chatlist.get_chat_id(chatlist_index)) @@ -2927,85 +3102,104 @@ mod tests { result } - #[test] - fn test_pinned() { + #[async_std::test] + async fn test_pinned() { let t = dummy_context(); // create 3 chats, wait 1 second in between to get a reliable order (we order by time) let mut msg = Message::new(Viewtype::Text); msg.text = Some("foo".to_string()); - let msg_id = add_device_msg(&t.ctx, None, Some(&mut msg)).unwrap(); + let msg_id = add_device_msg(&t.ctx, None, Some(&mut msg)).await.unwrap(); let chat_id1 = message::Message::load_from_db(&t.ctx, msg_id) + .await .unwrap() .chat_id; std::thread::sleep(std::time::Duration::from_millis(1000)); - let chat_id2 = create_by_contact_id(&t.ctx, DC_CONTACT_ID_SELF).unwrap(); + let chat_id2 = create_by_contact_id(&t.ctx, DC_CONTACT_ID_SELF) + .await + .unwrap(); std::thread::sleep(std::time::Duration::from_millis(1000)); - let chat_id3 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo").unwrap(); + let chat_id3 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo") + .await + .unwrap(); - let chatlist = get_chats_from_chat_list(&t.ctx, DC_GCL_NO_SPECIALS); + let chatlist = get_chats_from_chat_list(&t.ctx, DC_GCL_NO_SPECIALS).await; assert_eq!(chatlist, vec![chat_id3, chat_id2, chat_id1]); // pin assert!(chat_id1 .set_visibility(&t.ctx, ChatVisibility::Pinned) + .await .is_ok()); assert_eq!( Chat::load_from_db(&t.ctx, chat_id1) + .await .unwrap() .get_visibility(), ChatVisibility::Pinned ); // check if chat order changed - let chatlist = get_chats_from_chat_list(&t.ctx, DC_GCL_NO_SPECIALS); + let chatlist = get_chats_from_chat_list(&t.ctx, DC_GCL_NO_SPECIALS).await; assert_eq!(chatlist, vec![chat_id1, chat_id3, chat_id2]); // unpin assert!(chat_id1 .set_visibility(&t.ctx, ChatVisibility::Normal) + .await .is_ok()); assert_eq!( Chat::load_from_db(&t.ctx, chat_id1) + .await .unwrap() .get_visibility(), ChatVisibility::Normal ); // check if chat order changed back - let chatlist = get_chats_from_chat_list(&t.ctx, DC_GCL_NO_SPECIALS); + let chatlist = get_chats_from_chat_list(&t.ctx, DC_GCL_NO_SPECIALS).await; assert_eq!(chatlist, vec![chat_id3, chat_id2, chat_id1]); } #[async_std::test] async fn test_set_chat_name() { let t = dummy_context(); - let chat_id = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo").unwrap(); + let chat_id = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo") + .await + .unwrap(); assert_eq!( - Chat::load_from_db(&t.ctx, chat_id).unwrap().get_name(), + Chat::load_from_db(&t.ctx, chat_id) + .await + .unwrap() + .get_name(), "foo" ); set_chat_name(&t.ctx, chat_id, "bar").await.unwrap(); assert_eq!( - Chat::load_from_db(&t.ctx, chat_id).unwrap().get_name(), + Chat::load_from_db(&t.ctx, chat_id) + .await + .unwrap() + .get_name(), "bar" ); } - #[test] - fn test_create_same_chat_twice() { + #[async_std::test] + async fn test_create_same_chat_twice() { let context = dummy_context(); - let contact1 = Contact::create(&context.ctx, "bob", "bob@mail.de").unwrap(); + let contact1 = Contact::create(&context.ctx, "bob", "bob@mail.de") + .await + .unwrap(); assert_ne!(contact1, 0); - let chat_id = create_by_contact_id(&context.ctx, contact1).unwrap(); + let chat_id = create_by_contact_id(&context.ctx, contact1).await.unwrap(); assert!(!chat_id.is_special(), "chat_id too small {}", chat_id); - let chat = Chat::load_from_db(&context.ctx, chat_id).unwrap(); + let chat = Chat::load_from_db(&context.ctx, chat_id).await.unwrap(); - let chat2_id = create_by_contact_id(&context.ctx, contact1).unwrap(); + let chat2_id = create_by_contact_id(&context.ctx, contact1).await.unwrap(); assert_eq!(chat2_id, chat_id); - let chat2 = Chat::load_from_db(&context.ctx, chat2_id).unwrap(); + let chat2 = Chat::load_from_db(&context.ctx, chat2_id).await.unwrap(); assert_eq!(chat2.name, chat.name); } @@ -3013,39 +3207,61 @@ mod tests { #[async_std::test] async fn test_shall_attach_selfavatar() { let t = dummy_context(); - let chat_id = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo").unwrap(); - assert!(!shall_attach_selfavatar(&t.ctx, chat_id).unwrap()); + let chat_id = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo") + .await + .unwrap(); + assert!(!shall_attach_selfavatar(&t.ctx, chat_id).await.unwrap()); let (contact_id, _) = - Contact::add_or_lookup(&t.ctx, "", "foo@bar.org", Origin::IncomingUnknownTo).unwrap(); + Contact::add_or_lookup(&t.ctx, "", "foo@bar.org", Origin::IncomingUnknownTo) + .await + .unwrap(); add_contact_to_chat(&t.ctx, chat_id, contact_id).await; - assert!(!shall_attach_selfavatar(&t.ctx, chat_id).unwrap()); + assert!(!shall_attach_selfavatar(&t.ctx, chat_id).await.unwrap()); t.ctx.set_config(Config::Selfavatar, None).await.unwrap(); // setting to None also forces re-sending - assert!(shall_attach_selfavatar(&t.ctx, chat_id).unwrap()); + assert!(shall_attach_selfavatar(&t.ctx, chat_id).await.unwrap()); - assert!(chat_id.set_selfavatar_timestamp(&t.ctx, time()).is_ok()); - assert!(!shall_attach_selfavatar(&t.ctx, chat_id).unwrap()); + assert!(chat_id + .set_selfavatar_timestamp(&t.ctx, time()) + .await + .is_ok()); + assert!(!shall_attach_selfavatar(&t.ctx, chat_id).await.unwrap()); } - #[test] - fn test_set_mute_duration() { + #[async_std::test] + async fn test_set_mute_duration() { let t = dummy_context(); - let chat_id = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo").unwrap(); + let chat_id = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo") + .await + .unwrap(); // Initial assert_eq!( - Chat::load_from_db(&t.ctx, chat_id).unwrap().is_muted(), + Chat::load_from_db(&t.ctx, chat_id) + .await + .unwrap() + .is_muted(), false ); // Forever - set_muted(&t.ctx, chat_id, MuteDuration::Forever).unwrap(); + set_muted(&t.ctx, chat_id, MuteDuration::Forever) + .await + .unwrap(); assert_eq!( - Chat::load_from_db(&t.ctx, chat_id).unwrap().is_muted(), + Chat::load_from_db(&t.ctx, chat_id) + .await + .unwrap() + .is_muted(), true ); // unMute - set_muted(&t.ctx, chat_id, MuteDuration::NotMuted).unwrap(); + set_muted(&t.ctx, chat_id, MuteDuration::NotMuted) + .await + .unwrap(); assert_eq!( - Chat::load_from_db(&t.ctx, chat_id).unwrap().is_muted(), + Chat::load_from_db(&t.ctx, chat_id) + .await + .unwrap() + .is_muted(), false ); // Timed in the future @@ -3054,9 +3270,13 @@ mod tests { chat_id, MuteDuration::Until(SystemTime::now() + Duration::from_secs(3600)), ) + .await .unwrap(); assert_eq!( - Chat::load_from_db(&t.ctx, chat_id).unwrap().is_muted(), + Chat::load_from_db(&t.ctx, chat_id) + .await + .unwrap() + .is_muted(), true ); // Time in the past @@ -3065,9 +3285,13 @@ mod tests { chat_id, MuteDuration::Until(SystemTime::now() - Duration::from_secs(3600)), ) + .await .unwrap(); assert_eq!( - Chat::load_from_db(&t.ctx, chat_id).unwrap().is_muted(), + Chat::load_from_db(&t.ctx, chat_id) + .await + .unwrap() + .is_muted(), false ); } diff --git a/src/chatlist.rs b/src/chatlist.rs index 84491ce2d..fa8544654 100644 --- a/src/chatlist.rs +++ b/src/chatlist.rs @@ -85,7 +85,7 @@ impl Chatlist { /// are returned. /// `query_contact_id`: An optional contact ID for filtering the list. Only chats including this contact ID /// are returned. - pub fn try_load( + pub async fn try_load( context: &Context, listflags: usize, query: Option<&str>, @@ -139,11 +139,13 @@ impl Chatlist { params![MessageState::OutDraft, query_contact_id as i32, ChatVisibility::Pinned], process_row, process_rows, - )? + ).await? } else if 0 != listflags & DC_GCL_ARCHIVED_ONLY { // show archived chats - context.sql.query_map( - "SELECT c.id, m.id + context + .sql + .query_map( + "SELECT c.id, m.id FROM chats c LEFT JOIN msgs m ON c.id=m.chat_id @@ -157,23 +159,26 @@ impl Chatlist { AND c.archived=1 GROUP BY c.id ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;", - params![MessageState::OutDraft], - process_row, - process_rows, - )? + params![MessageState::OutDraft], + process_row, + process_rows, + ) + .await? } else if let Some(query) = query { let query = query.trim().to_string(); ensure!(!query.is_empty(), "missing query"); // allow searching over special names that may change at any time // when the ui calls set_stock_translation() - if let Err(err) = update_special_chat_names(context) { + if let Err(err) = update_special_chat_names(context).await { warn!(context, "cannot update special chat names: {:?}", err) } let str_like_cmd = format!("%{}%", query); - context.sql.query_map( - "SELECT c.id, m.id + context + .sql + .query_map( + "SELECT c.id, m.id FROM chats c LEFT JOIN msgs m ON c.id=m.chat_id @@ -187,10 +192,11 @@ impl Chatlist { AND c.name LIKE ? GROUP BY c.id ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;", - params![MessageState::OutDraft, str_like_cmd], - process_row, - process_rows, - )? + params![MessageState::OutDraft, str_like_cmd], + process_row, + process_rows, + ) + .await? } else { // show normal chatlist let sort_id_up = if 0 != listflags & DC_GCL_FOR_FORWARDING { @@ -218,9 +224,10 @@ impl Chatlist { params![MessageState::OutDraft, ChatVisibility::Archived, sort_id_up, ChatVisibility::Pinned], process_row, process_rows, - )?; + ).await?; if 0 == listflags & DC_GCL_NO_SPECIALS { - if let Some(last_deaddrop_fresh_msg_id) = get_last_deaddrop_fresh_msg(context) { + if let Some(last_deaddrop_fresh_msg_id) = get_last_deaddrop_fresh_msg(context).await + { if 0 == listflags & DC_GCL_FOR_FORWARDING { ids.insert( 0, @@ -233,7 +240,7 @@ impl Chatlist { ids }; - if add_archived_link_item && dc_get_archived_cnt(context) > 0 { + if add_archived_link_item && dc_get_archived_cnt(context).await > 0 { if ids.is_empty() && 0 != listflags & DC_GCL_ADD_ALLDONE_HINT { ids.push((ChatId::new(DC_CHAT_ID_ALLDONE_HINT), MsgId::new(0))); } @@ -285,7 +292,7 @@ impl Chatlist { /// - dc_lot_t::timestamp: the timestamp of the message. 0 if not applicable. /// - dc_lot_t::state: The state of the message as one of the DC_STATE_* constants (see #dc_msg_get_state()). // 0 if not applicable. - pub fn get_summary(&self, context: &Context, index: usize, chat: Option<&Chat>) -> Lot { + pub async fn get_summary(&self, context: &Context, index: usize, chat: Option<&Chat>) -> Lot { // The summary is created by the chat, not by the last message. // This is because we may want to display drafts here or stuff as // "is typing". @@ -300,7 +307,7 @@ impl Chatlist { let chat_loaded: Chat; let chat = if let Some(chat) = chat { chat - } else if let Ok(chat) = Chat::load_from_db(context, self.ids[index].0) { + } else if let Ok(chat) = Chat::load_from_db(context, self.ids[index].0).await { chat_loaded = chat; &chat_loaded } else { @@ -310,11 +317,11 @@ impl Chatlist { let lastmsg_id = self.ids[index].1; let mut lastcontact = None; - let lastmsg = if let Ok(lastmsg) = Message::load_from_db(context, lastmsg_id) { + let lastmsg = if let Ok(lastmsg) = Message::load_from_db(context, lastmsg_id).await { if lastmsg.from_id != DC_CONTACT_ID_SELF && (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup) { - lastcontact = Contact::load_from_db(context, lastmsg.from_id).ok(); + lastcontact = Contact::load_from_db(context, lastmsg.from_id).await.ok(); } Some(lastmsg) @@ -340,7 +347,7 @@ impl Chatlist { } /// Returns the number of archived chats -pub fn dc_get_archived_cnt(context: &Context) -> u32 { +pub async fn dc_get_archived_cnt(context: &Context) -> u32 { context .sql .query_get_value( @@ -348,26 +355,30 @@ pub fn dc_get_archived_cnt(context: &Context) -> u32 { "SELECT COUNT(*) FROM chats WHERE blocked=0 AND archived=1;", params![], ) + .await .unwrap_or_default() } -fn get_last_deaddrop_fresh_msg(context: &Context) -> Option { +async fn get_last_deaddrop_fresh_msg(context: &Context) -> Option { // We have an index over the state-column, this should be // sufficient as there are typically only few fresh messages. - context.sql.query_get_value( - context, - concat!( - "SELECT m.id", - " FROM msgs m", - " LEFT JOIN chats c", - " ON c.id=m.chat_id", - " WHERE m.state=10", - " AND m.hidden=0", - " AND c.blocked=2", - " ORDER BY m.timestamp DESC, m.id DESC;" - ), - params![], - ) + context + .sql + .query_get_value( + context, + concat!( + "SELECT m.id", + " FROM msgs m", + " LEFT JOIN chats c", + " ON c.id=m.chat_id", + " WHERE m.state=10", + " AND m.hidden=0", + " AND c.blocked=2", + " ORDER BY m.timestamp DESC, m.id DESC;" + ), + params![], + ) + .await } #[cfg(test)] @@ -376,15 +387,21 @@ mod tests { use crate::test_utils::*; - #[test] - fn test_try_load() { + #[async_std::test] + async fn test_try_load() { let t = dummy_context(); - let chat_id1 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "a chat").unwrap(); - let chat_id2 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "b chat").unwrap(); - let chat_id3 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "c chat").unwrap(); + let chat_id1 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "a chat") + .await + .unwrap(); + let chat_id2 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "b chat") + .await + .unwrap(); + let chat_id3 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "c chat") + .await + .unwrap(); // check that the chatlist starts with the most recent message - let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap(); + let chats = Chatlist::try_load(&t.ctx, 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); @@ -394,20 +411,27 @@ mod tests { let mut msg = Message::new(Viewtype::Text); msg.set_text(Some("hello".to_string())); chat_id2.set_draft(&t.ctx, Some(&mut msg)); - let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap(); + let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); assert_eq!(chats.get_chat_id(0), chat_id2); // check chatlist query and archive functionality - let chats = Chatlist::try_load(&t.ctx, 0, Some("b"), None).unwrap(); + let chats = Chatlist::try_load(&t.ctx, 0, Some("b"), None) + .await + .unwrap(); assert_eq!(chats.len(), 1); - let chats = Chatlist::try_load(&t.ctx, DC_GCL_ARCHIVED_ONLY, None, None).unwrap(); + let chats = Chatlist::try_load(&t.ctx, DC_GCL_ARCHIVED_ONLY, None, None) + .await + .unwrap(); assert_eq!(chats.len(), 0); chat_id1 .set_visibility(&t.ctx, ChatVisibility::Archived) + .await .ok(); - let chats = Chatlist::try_load(&t.ctx, DC_GCL_ARCHIVED_ONLY, None, None).unwrap(); + let chats = Chatlist::try_load(&t.ctx, DC_GCL_ARCHIVED_ONLY, None, None) + .await + .unwrap(); assert_eq!(chats.len(), 1); } @@ -427,40 +451,50 @@ mod tests { .is_self_talk()); } - #[test] - fn test_search_special_chat_names() { + #[async_std::test] + async fn test_search_special_chat_names() { let t = dummy_context(); t.ctx.update_device_chats().unwrap(); - let chats = Chatlist::try_load(&t.ctx, 0, Some("t-1234-s"), None).unwrap(); + let chats = Chatlist::try_load(&t.ctx, 0, Some("t-1234-s"), None) + .await + .unwrap(); assert_eq!(chats.len(), 0); - let chats = Chatlist::try_load(&t.ctx, 0, Some("t-5678-b"), None).unwrap(); + let chats = Chatlist::try_load(&t.ctx, 0, Some("t-5678-b"), None) + .await + .unwrap(); assert_eq!(chats.len(), 0); t.ctx .set_stock_translation(StockMessage::SavedMessages, "test-1234-save".to_string()) .unwrap(); - let chats = Chatlist::try_load(&t.ctx, 0, Some("t-1234-s"), None).unwrap(); + let chats = Chatlist::try_load(&t.ctx, 0, Some("t-1234-s"), None) + .await + .unwrap(); assert_eq!(chats.len(), 1); t.ctx .set_stock_translation(StockMessage::DeviceMessages, "test-5678-babbel".to_string()) .unwrap(); - let chats = Chatlist::try_load(&t.ctx, 0, Some("t-5678-b"), None).unwrap(); + let chats = Chatlist::try_load(&t.ctx, 0, Some("t-5678-b"), None) + .await + .unwrap(); assert_eq!(chats.len(), 1); } - #[test] - fn test_get_summary_unwrap() { + #[async_std::test] + async fn test_get_summary_unwrap() { let t = dummy_context(); - let chat_id1 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "a chat").unwrap(); + let chat_id1 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "a chat") + .await + .unwrap(); let mut msg = Message::new(Viewtype::Text); msg.set_text(Some("foo:\nbar \r\n test".to_string())); chat_id1.set_draft(&t.ctx, Some(&mut msg)); - let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap(); - let summary = chats.get_summary(&t.ctx, 0, None); + let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); + let summary = chats.get_summary(&t.ctx, 0, None).await; assert_eq!(summary.get_text2().unwrap(), "foo: bar test"); // the linebreak should be removed from summary } } diff --git a/src/config.rs b/src/config.rs index 9d437ef6b..62c02e510 100644 --- a/src/config.rs +++ b/src/config.rs @@ -95,16 +95,16 @@ pub enum Config { impl Context { /// Get a configuration key. Returns `None` if no value is set, and no default value found. - pub fn get_config(&self, key: Config) -> Option { + pub async fn get_config(&self, key: Config) -> Option { let value = match key { Config::Selfavatar => { - let rel_path = self.sql.get_raw_config(self, key); + let rel_path = self.sql.get_raw_config(self, key).await; rel_path.map(|p| dc_get_abs_path(self, &p).to_string_lossy().into_owned()) } Config::SysVersion => Some((&*DC_VERSION_STR).clone()), Config::SysMsgsizeMaxRecommended => Some(format!("{}", RECOMMENDED_FILE_SIZE)), Config::SysConfigKeys => Some(get_config_keys_string()), - _ => self.sql.get_raw_config(self, key), + _ => self.sql.get_raw_config(self, key).await, }; if value.is_some() { @@ -118,14 +118,15 @@ impl Context { } } - pub fn get_config_int(&self, key: Config) -> i32 { + pub async fn get_config_int(&self, key: Config) -> i32 { self.get_config(key) + .await .and_then(|s| s.parse().ok()) .unwrap_or_default() } - pub fn get_config_bool(&self, key: Config) -> bool { - self.get_config_int(key) != 0 + pub async fn get_config_bool(&self, key: Config) -> bool { + self.get_config_int(key).await != 0 } /// Set the given config key. @@ -134,30 +135,34 @@ impl Context { match key { Config::Selfavatar => { self.sql - .execute("UPDATE contacts SET selfavatar_sent=0;", NO_PARAMS)?; + .execute("UPDATE contacts SET selfavatar_sent=0;", NO_PARAMS) + .await?; self.sql - .set_raw_config_bool(self, "attach_selfavatar", true)?; + .set_raw_config_bool(self, "attach_selfavatar", true) + .await?; match value { Some(value) => { let blob = BlobObject::new_from_path(&self, value)?; blob.recode_to_avatar_size(self)?; - self.sql.set_raw_config(self, key, Some(blob.as_name())) + self.sql + .set_raw_config(self, key, Some(blob.as_name())) + .await } - None => self.sql.set_raw_config(self, key, None), + None => self.sql.set_raw_config(self, key, None).await, } } Config::InboxWatch => { - let ret = self.sql.set_raw_config(self, key, value); + let ret = self.sql.set_raw_config(self, key, value).await; interrupt_inbox_idle(self).await; ret } Config::SentboxWatch => { - let ret = self.sql.set_raw_config(self, key, value); + let ret = self.sql.set_raw_config(self, key, value).await; interrupt_sentbox_idle(self).await; ret } Config::MvboxWatch => { - let ret = self.sql.set_raw_config(self, key, value); + let ret = self.sql.set_raw_config(self, key, value).await; interrupt_mvbox_idle(self).await; ret } @@ -169,9 +174,9 @@ impl Context { value }; - self.sql.set_raw_config(self, key, val) + self.sql.set_raw_config(self, key, val).await } - _ => self.sql.set_raw_config(self, key, value), + _ => self.sql.set_raw_config(self, key, value).await, } } } @@ -234,7 +239,7 @@ mod tests { .unwrap(); assert!(avatar_blob.exists()); assert!(std::fs::metadata(&avatar_blob).unwrap().len() < avatar_bytes.len() as u64); - let avatar_cfg = t.ctx.get_config(Config::Selfavatar); + let avatar_cfg = t.ctx.get_config(Config::Selfavatar).await; assert_eq!(avatar_cfg, avatar_blob.to_str().map(|s| s.to_string())); let img = image::open(avatar_src).unwrap(); @@ -264,7 +269,7 @@ mod tests { .set_config(Config::Selfavatar, Some(&avatar_src.to_str().unwrap())) .await .unwrap(); - let avatar_cfg = t.ctx.get_config(Config::Selfavatar); + let avatar_cfg = t.ctx.get_config(Config::Selfavatar).await; assert_eq!(avatar_cfg, avatar_src.to_str().map(|s| s.to_string())); let img = image::open(avatar_src).unwrap(); diff --git a/src/configure/mod.rs b/src/configure/mod.rs index 111f943d6..d069132d2 100644 --- a/src/configure/mod.rs +++ b/src/configure/mod.rs @@ -42,8 +42,8 @@ impl Context { } /// Checks if the context is already configured. - pub fn is_configured(&self) -> bool { - self.sql.get_raw_config_bool(self, "configured") + pub async fn is_configured(&self) -> bool { + self.sql.get_raw_config_bool(self, "configured").await } } @@ -52,7 +52,7 @@ impl Context { ******************************************************************************/ #[allow(clippy::cognitive_complexity)] pub(crate) async fn job_configure_imap(context: &Context) -> job::Status { - if !context.sql.is_open() { + if !context.sql.is_open().await { error!(context, "Cannot configure, database not opened.",); progress!(context, 0); return job::Status::Finished(Err(format_err!("Database not opened"))); @@ -75,7 +75,7 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status { info!(context, "Configure ...",); // Variables that are shared between steps: - let mut param = LoginParam::from_database(context, ""); + let mut param = LoginParam::from_database(context, "").await; // need all vars here to be mutable because rust thinks the same step could be called multiple times // and also initialize, because otherwise rust thinks it's used while unitilized, even if thats not the case as the loop goes only forward let mut param_domain = "undefined.undefined".to_owned(); @@ -115,6 +115,7 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status { context .sql .set_raw_config(context, "addr", Some(param.addr.as_str())) + .await .ok(); } progress!(context, 20); @@ -352,8 +353,8 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status { } 16 => { progress!(context, 900); - let create_mvbox = context.get_config_bool(Config::MvboxWatch) - || context.get_config_bool(Config::MvboxMove); + let create_mvbox = context.get_config_bool(Config::MvboxWatch).await + || context.get_config_bool(Config::MvboxMove).await; let imap = &context.inbox_thread.imap; if let Err(err) = imap.ensure_configured_folders(context, create_mvbox).await { warn!(context, "configuring folders failed: {:?}", err); @@ -376,11 +377,13 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status { context, "configured_", /*the trailing underscore is correct*/ ) + .await .ok(); context .sql .set_raw_config_bool(context, "configured", true) + .await .ok(); true } @@ -389,7 +392,7 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status { // we generate the keypair just now - we could also postpone this until the first message is sent, however, // this may result in a unexpected and annoying delay when the user sends his very first message // (~30 seconds on a Moto G4 play) and might looks as if message sending is always that slow. - success = e2ee::ensure_secret_key_exists(context).is_ok(); + success = e2ee::ensure_secret_key_exists(context).await.is_ok(); info!(context, "key generation completed"); progress!(context, 940); break; // We are done here @@ -416,11 +419,15 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status { // this way, the parameters visible to the ui are always in-sync with the current configuration. if success { LoginParam::from_database(context, "") + .await .save_to_database(context, "configured_raw_") + .await .ok(); } else { LoginParam::from_database(context, "configured_raw_") + .await .save_to_database(context, "") + .await .ok(); } @@ -428,7 +435,10 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status { if !provider.after_login_hint.is_empty() { let mut msg = Message::new(Viewtype::Text); msg.text = Some(provider.after_login_hint.to_string()); - if chat::add_device_msg(context, Some("core-provider-info"), Some(&mut msg)).is_err() { + if chat::add_device_msg(context, Some("core-provider-info"), Some(&mut msg)) + .await + .is_err() + { warn!(context, "cannot add after_login_hint as core-provider-info"); } } diff --git a/src/contact.rs b/src/contact.rs index b3d6672b3..29a9bab9a 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -21,7 +21,6 @@ use crate::message::{MessageState, MsgId}; use crate::mimeparser::AvatarAction; use crate::param::*; use crate::peerstate::*; -use crate::sql; use crate::stock::StockMessage; /// Contacts with at least this origin value are shown in the contact list. @@ -164,29 +163,33 @@ pub enum VerifiedStatus { } impl Contact { - pub fn load_from_db(context: &Context, contact_id: u32) -> crate::sql::Result { - let mut res = context.sql.query_row( - "SELECT c.name, c.addr, c.origin, c.blocked, c.authname, c.param + pub async fn load_from_db(context: &Context, contact_id: u32) -> crate::sql::Result { + let mut res = context + .sql + .query_row( + "SELECT c.name, c.addr, c.origin, c.blocked, c.authname, c.param FROM contacts c WHERE c.id=?;", - params![contact_id as i32], - |row| { - let contact = Self { - id: contact_id, - name: row.get::<_, String>(0)?, - authname: row.get::<_, String>(4)?, - addr: row.get::<_, String>(1)?, - blocked: row.get::<_, Option>(3)?.unwrap_or_default() != 0, - origin: row.get(2)?, - param: row.get::<_, String>(5)?.parse().unwrap_or_default(), - }; - Ok(contact) - }, - )?; + params![contact_id as i32], + |row| { + let contact = Self { + id: contact_id, + name: row.get::<_, String>(0)?, + authname: row.get::<_, String>(4)?, + addr: row.get::<_, String>(1)?, + blocked: row.get::<_, Option>(3)?.unwrap_or_default() != 0, + origin: row.get(2)?, + param: row.get::<_, String>(5)?.parse().unwrap_or_default(), + }; + Ok(contact) + }, + ) + .await?; if contact_id == DC_CONTACT_ID_SELF { res.name = context.stock_str(StockMessage::SelfMsg).to_string(); res.addr = context .get_config(Config::ConfiguredAddr) + .await .unwrap_or_default(); } else if contact_id == DC_CONTACT_ID_DEVICE { res.name = context.stock_str(StockMessage::DeviceMessages).to_string(); @@ -201,20 +204,21 @@ impl Contact { } /// Check if a contact is blocked. - pub fn is_blocked_load(context: &Context, id: u32) -> bool { + pub async fn is_blocked_load(context: &Context, id: u32) -> bool { Self::load_from_db(context, id) + .await .map(|contact| contact.blocked) .unwrap_or_default() } /// Block the given contact. - pub fn block(context: &Context, id: u32) { - set_block_contact(context, id, true); + pub async fn block(context: &Context, id: u32) { + set_block_contact(context, id, true).await; } /// Unblock the given contact. - pub fn unblock(context: &Context, id: u32) { - set_block_contact(context, id, false); + pub async fn unblock(context: &Context, id: u32) { + set_block_contact(context, id, false).await; } /// Add a single contact as a result of an _explicit_ user action. @@ -226,15 +230,19 @@ impl Contact { /// a bunch of addresses. /// /// May result in a `#DC_EVENT_CONTACTS_CHANGED` event. - pub fn create(context: &Context, name: impl AsRef, addr: impl AsRef) -> Result { + pub async fn create( + context: &Context, + name: impl AsRef, + addr: impl AsRef, + ) -> Result { ensure!( !addr.as_ref().is_empty(), "Cannot create contact with empty address" ); let (contact_id, sth_modified) = - Contact::add_or_lookup(context, name, addr, Origin::ManuallyCreated)?; - let blocked = Contact::is_blocked_load(context, contact_id); + Contact::add_or_lookup(context, name, addr, Origin::ManuallyCreated).await?; + let blocked = Contact::is_blocked_load(context, contact_id).await; context.call_cb(Event::ContactsChanged( if sth_modified == Modifier::Created { Some(contact_id) @@ -253,14 +261,15 @@ impl Contact { /// as *noticed*. See also dc_marknoticed_chat() and dc_markseen_msgs() /// /// Calling this function usually results in the event `#DC_EVENT_MSGS_CHANGED`. - pub fn mark_noticed(context: &Context, id: u32) { - if sql::execute( - context, - &context.sql, - "UPDATE msgs SET state=? WHERE from_id=? AND state=?;", - params![MessageState::InNoticed, id as i32, MessageState::InFresh], - ) - .is_ok() + pub async fn mark_noticed(context: &Context, id: u32) { + if context + .sql + .execute( + "UPDATE msgs SET state=? WHERE from_id=? AND state=?;", + params![MessageState::InNoticed, id as i32, MessageState::InFresh], + ) + .await + .is_ok() { context.call_cb(Event::MsgsChanged { chat_id: ChatId::new(0), @@ -274,7 +283,7 @@ impl Contact { /// /// To validate an e-mail address independently of the contact database /// use `dc_may_be_valid_addr()`. - pub fn lookup_id_by_addr(context: &Context, addr: impl AsRef) -> u32 { + pub async fn lookup_id_by_addr(context: &Context, addr: impl AsRef) -> u32 { if addr.as_ref().is_empty() { return 0; } @@ -282,6 +291,7 @@ impl Contact { let addr_normalized = addr_normalize(addr.as_ref()); let addr_self = context .get_config(Config::ConfiguredAddr) + .await .unwrap_or_default(); if addr_cmp(addr_normalized, addr_self) { @@ -296,7 +306,7 @@ impl Contact { DC_CONTACT_ID_LAST_SPECIAL as i32, DC_ORIGIN_MIN_CONTACT_LIST, ], - ).unwrap_or_default() + ).await.unwrap_or_default() } /// Lookup a contact and create it if it does not exist yet. @@ -324,7 +334,7 @@ impl Contact { /// Depending on the origin, both, "row_name" and "row_authname" are updated from "name". /// /// Returns the contact_id and a `Modifier` value indicating if a modification occured. - pub(crate) fn add_or_lookup( + pub(crate) async fn add_or_lookup( context: &Context, name: impl AsRef, addr: impl AsRef, @@ -341,6 +351,7 @@ impl Contact { let addr = addr_normalize(addr.as_ref()); let addr_self = context .get_config(Config::ConfiguredAddr) + .await .unwrap_or_default(); if addr_cmp(addr, addr_self) { @@ -396,7 +407,8 @@ impl Contact { Ok((row_id, row_name, row_addr, row_origin, row_authname)) }, - ) { + ) + .await { row_id = id; if origin as i32 >= row_origin as i32 && addr != row_addr { update_addr = true; @@ -412,37 +424,36 @@ impl Contact { &row_name }; - sql::execute( - context, - &context.sql, - "UPDATE contacts SET name=?, addr=?, origin=?, authname=? WHERE id=?;", - params![ - new_name, - if update_addr { addr } else { &row_addr }, - if origin > row_origin { - origin - } else { - row_origin - }, - if update_authname { - name.as_ref() - } else { - &row_authname - }, - row_id - ], - ) - .ok(); + context + .sql + .execute( + "UPDATE contacts SET name=?, addr=?, origin=?, authname=? WHERE id=?;", + params![ + new_name, + if update_addr { addr } else { &row_addr }, + if origin > row_origin { + origin + } else { + row_origin + }, + if update_authname { + name.as_ref() + } else { + &row_authname + }, + row_id + ], + ) + .await + .ok(); if update_name { // Update the contact name also if it is used as a group name. // This is one of the few duplicated data, however, getting the chat list is easier this way. - sql::execute( - context, - &context.sql, + context.sql.execute( "UPDATE chats SET name=? WHERE type=? AND id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?);", params![new_name, Chattype::Single, row_id] - ).ok(); + ).await.ok(); } sth_modified = Modifier::Modified; } @@ -451,20 +462,24 @@ impl Contact { update_authname = true; } - if sql::execute( - context, - &context.sql, - "INSERT INTO contacts (name, addr, origin, authname) VALUES(?, ?, ?, ?);", - params![ - name.as_ref(), - addr, - origin, - if update_authname { name.as_ref() } else { "" } - ], - ) - .is_ok() + if context + .sql + .execute( + "INSERT INTO contacts (name, addr, origin, authname) VALUES(?, ?, ?, ?);", + params![ + name.as_ref(), + addr, + origin, + if update_authname { name.as_ref() } else { "" } + ], + ) + .await + .is_ok() { - row_id = sql::get_rowid(context, &context.sql, "contacts", "addr", addr); + row_id = context + .sql + .get_rowid(context, "contacts", "addr", addr) + .await?; sth_modified = Modifier::Created; info!(context, "added contact id={} addr={}", row_id, addr); } else { @@ -492,12 +507,12 @@ impl Contact { /// The `addr_book` is a multiline string in the format `Name one\nAddress one\nName two\nAddress two`. /// /// Returns the number of modified contacts. - pub fn add_address_book(context: &Context, addr_book: impl AsRef) -> Result { + pub async fn add_address_book(context: &Context, addr_book: impl AsRef) -> Result { let mut modify_cnt = 0; for (name, addr) in split_address_book(addr_book.as_ref()).into_iter() { let name = normalize_name(name); - match Contact::add_or_lookup(context, name, addr, Origin::AddressBook) { + match Contact::add_or_lookup(context, name, addr, Origin::AddressBook).await { Err(err) => { warn!( context, @@ -527,13 +542,14 @@ impl Contact { /// - if the flag DC_GCL_VERIFIED_ONLY is set, only verified contacts are returned. /// if DC_GCL_VERIFIED_ONLY is not set, verified and unverified contacts are returned. /// `query` is a string to filter the list. - pub fn get_all( + pub async fn get_all( context: &Context, listflags: u32, query: Option>, ) -> Result> { let self_addr = context .get_config(Config::ConfiguredAddr) + .await .unwrap_or_default(); let mut add_self = false; @@ -549,8 +565,10 @@ impl Contact { .map(|s| s.as_ref().to_string()) .unwrap_or_default() ); - context.sql.query_map( - "SELECT c.id FROM contacts c \ + context + .sql + .query_map( + "SELECT c.id FROM contacts c \ LEFT JOIN acpeerstates ps ON c.addr=ps.addr \ WHERE c.addr!=?1 \ AND c.id>?2 \ @@ -559,24 +577,28 @@ impl Contact { AND (c.name LIKE ?4 OR c.addr LIKE ?5) \ AND (1=?6 OR LENGTH(ps.verified_key_fingerprint)!=0) \ ORDER BY LOWER(c.name||c.addr),c.id;", - params![ - self_addr, - DC_CONTACT_ID_LAST_SPECIAL as i32, - Origin::IncomingReplyTo, - &s3str_like_cmd, - &s3str_like_cmd, - if flag_verified_only { 0 } else { 1 }, - ], - |row| row.get::<_, i32>(0), - |ids| { - for id in ids { - ret.push(id? as u32); - } - Ok(()) - }, - )?; + params![ + self_addr, + DC_CONTACT_ID_LAST_SPECIAL as i32, + Origin::IncomingReplyTo, + &s3str_like_cmd, + &s3str_like_cmd, + if flag_verified_only { 0 } else { 1 }, + ], + |row| row.get::<_, i32>(0), + |ids| { + for id in ids { + ret.push(id? as u32); + } + Ok(()) + }, + ) + .await?; - let self_name = context.get_config(Config::Displayname).unwrap_or_default(); + let self_name = context + .get_config(Config::Displayname) + .await + .unwrap_or_default(); let self_name2 = context.stock_str(StockMessage::SelfMsg); if let Some(query) = query { @@ -602,7 +624,7 @@ impl Contact { } Ok(()) } - )?; + ).await?; } if flag_add_self && add_self { @@ -612,7 +634,7 @@ impl Contact { Ok(ret) } - pub fn get_blocked_cnt(context: &Context) -> usize { + pub async fn get_blocked_cnt(context: &Context) -> usize { context .sql .query_get_value::<_, isize>( @@ -620,11 +642,12 @@ impl Contact { "SELECT COUNT(*) FROM contacts WHERE id>? AND blocked!=0", params![DC_CONTACT_ID_LAST_SPECIAL as i32], ) + .await .unwrap_or_default() as usize } /// Get blocked contacts. - pub fn get_all_blocked(context: &Context) -> Vec { + pub async fn get_all_blocked(context: &Context) -> Vec { context .sql .query_map( @@ -636,6 +659,7 @@ impl Contact { .map_err(Into::into) }, ) + .await .unwrap_or_default() } @@ -644,14 +668,14 @@ impl Contact { /// This function returns a string explaining the encryption state /// of the contact and if the connection is encrypted the /// fingerprints of the keys involved. - pub fn get_encrinfo(context: &Context, contact_id: u32) -> Result { + pub async fn get_encrinfo(context: &Context, contact_id: u32) -> Result { let mut ret = String::new(); - if let Ok(contact) = Contact::load_from_db(context, contact_id) { + if let Ok(contact) = Contact::load_from_db(context, contact_id).await { let peerstate = Peerstate::from_addr(context, &context.sql, &contact.addr); - let loginparam = LoginParam::from_database(context, "configured_"); + let loginparam = LoginParam::from_database(context, "configured_").await; - let mut self_key = Key::from_self_public(context, &loginparam.addr, &context.sql); + let mut self_key = Key::from_self_public(context, &loginparam.addr, &context.sql).await; if peerstate.is_some() && peerstate @@ -668,8 +692,8 @@ impl Contact { }); ret += &p; if self_key.is_none() { - e2ee::ensure_secret_key_exists(context)?; - self_key = Key::from_self_public(context, &loginparam.addr, &context.sql); + e2ee::ensure_secret_key_exists(context).await?; + self_key = Key::from_self_public(context, &loginparam.addr, &context.sql).await; } let p = context.stock_str(StockMessage::FingerPrints); ret += &format!(" {}:", p); @@ -718,7 +742,7 @@ impl Contact { /// possible as the contact is in use. In this case, the contact can be blocked. /// /// May result in a `#DC_EVENT_CONTACTS_CHANGED` event. - pub fn delete(context: &Context, contact_id: u32) -> Result<()> { + pub async fn delete(context: &Context, contact_id: u32) -> Result<()> { ensure!( contact_id > DC_CONTACT_ID_LAST_SPECIAL, "Can not delete special contact" @@ -731,6 +755,7 @@ impl Contact { "SELECT COUNT(*) FROM chats_contacts WHERE contact_id=?;", params![contact_id as i32], ) + .await .unwrap_or_default(); let count_msgs: i32 = if count_contacts > 0 { @@ -741,18 +766,21 @@ impl Contact { "SELECT COUNT(*) FROM msgs WHERE from_id=? OR to_id=?;", params![contact_id as i32, contact_id as i32], ) + .await .unwrap_or_default() } else { 0 }; if count_msgs == 0 { - match sql::execute( - context, - &context.sql, - "DELETE FROM contacts WHERE id=?;", - params![contact_id as i32], - ) { + match context + .sql + .execute( + "DELETE FROM contacts WHERE id=?;", + params![contact_id as i32], + ) + .await + { Ok(_) => { context.call_cb(Event::ContactsChanged(None)); return Ok(()); @@ -776,17 +804,20 @@ impl Contact { /// For contact DC_CONTACT_ID_SELF (1), the function returns sth. /// like "Me" in the selected language and the email address /// defined by dc_set_config(). - pub fn get_by_id(context: &Context, contact_id: u32) -> Result { - Ok(Contact::load_from_db(context, contact_id)?) + pub async fn get_by_id(context: &Context, contact_id: u32) -> Result { + let contact = Contact::load_from_db(context, contact_id).await?; + + Ok(contact) } - pub fn update_param(&mut self, context: &Context) -> Result<()> { - sql::execute( - context, - &context.sql, - "UPDATE contacts SET param=? WHERE id=?", - params![self.param.to_string(), self.id as i32], - )?; + pub async fn update_param(&mut self, context: &Context) -> Result<()> { + context + .sql + .execute( + "UPDATE contacts SET param=? WHERE id=?", + params![self.param.to_string(), self.id as i32], + ) + .await?; Ok(()) } @@ -856,9 +887,9 @@ impl Contact { /// Get the contact's profile image. /// This is the image set by each remote user on their own /// using dc_set_config(context, "selfavatar", image). - pub fn get_profile_image(&self, context: &Context) -> Option { + pub async fn get_profile_image(&self, context: &Context) -> Option { if self.id == DC_CONTACT_ID_SELF { - if let Some(p) = context.get_config(Config::Selfavatar) { + if let Some(p) = context.get_config(Config::Selfavatar).await { return Some(PathBuf::from(p)); } } else if let Some(image_rel) = self.param.get(Param::ProfileImage) { @@ -916,12 +947,16 @@ impl Contact { VerifiedStatus::Unverified } - pub fn addr_equals_contact(context: &Context, addr: impl AsRef, contact_id: u32) -> bool { + pub async fn addr_equals_contact( + context: &Context, + addr: impl AsRef, + contact_id: u32, + ) -> bool { if addr.as_ref().is_empty() { return false; } - if let Ok(contact) = Contact::load_from_db(context, contact_id) { + if let Ok(contact) = Contact::load_from_db(context, contact_id).await { if !contact.addr.is_empty() { let normalized_addr = addr_normalize(addr.as_ref()); if contact.addr == normalized_addr { @@ -933,8 +968,8 @@ impl Contact { false } - pub fn get_real_cnt(context: &Context) -> usize { - if !context.sql.is_open() { + pub async fn get_real_cnt(context: &Context) -> usize { + if !context.sql.is_open().await { return 0; } @@ -945,11 +980,12 @@ impl Contact { "SELECT COUNT(*) FROM contacts WHERE id>?;", params![DC_CONTACT_ID_LAST_SPECIAL as i32], ) + .await .unwrap_or_default() as usize } - pub fn real_exists_by_id(context: &Context, contact_id: u32) -> bool { - if !context.sql.is_open() || contact_id <= DC_CONTACT_ID_LAST_SPECIAL { + pub async fn real_exists_by_id(context: &Context, contact_id: u32) -> bool { + if !context.sql.is_open().await || contact_id <= DC_CONTACT_ID_LAST_SPECIAL { return false; } @@ -959,16 +995,18 @@ impl Contact { "SELECT id FROM contacts WHERE id=?;", params![contact_id as i32], ) + .await .unwrap_or_default() } - pub fn scaleup_origin_by_id(context: &Context, contact_id: u32, origin: Origin) -> bool { + pub async fn scaleup_origin_by_id(context: &Context, contact_id: u32, origin: Origin) -> bool { context .sql .execute( "UPDATE contacts SET origin=? WHERE id=? AND origin &str { norm } -fn set_block_contact(context: &Context, contact_id: u32, new_blocking: bool) { +async fn set_block_contact(context: &Context, contact_id: u32, new_blocking: bool) { if contact_id <= DC_CONTACT_ID_LAST_SPECIAL { return; } - if let Ok(contact) = Contact::load_from_db(context, contact_id) { + if let Ok(contact) = Contact::load_from_db(context, contact_id).await { if contact.blocked != new_blocking - && sql::execute( - context, - &context.sql, - "UPDATE contacts SET blocked=? WHERE id=?;", - params![new_blocking as i32, contact_id as i32], - ) - .is_ok() + && context + .sql + .execute( + "UPDATE contacts SET blocked=? WHERE id=?;", + params![new_blocking as i32, contact_id as i32], + ) + .await + .is_ok() { // also (un)block all chats with _only_ this contact - we do not delete them to allow a // non-destructive blocking->unblocking. // (Maybe, beside normal chats (type=100) we should also block group chats with only this user. // However, I'm not sure about this point; it may be confusing if the user wants to add other people; // this would result in recreating the same group...) - if sql::execute( - context, - &context.sql, + if context.sql.execute( "UPDATE chats SET blocked=? WHERE type=? AND id IN (SELECT chat_id FROM chats_contacts WHERE contact_id=?);", params![new_blocking, 100, contact_id as i32], - ).is_ok() { + ).await.is_ok() { Contact::mark_noticed(context, contact_id); context.call_cb(Event::ContactsChanged(None)); } @@ -1028,14 +1065,14 @@ fn set_block_contact(context: &Context, contact_id: u32, new_blocking: bool) { } } -pub(crate) fn set_profile_image( +pub(crate) async fn set_profile_image( context: &Context, contact_id: u32, profile_image: &AvatarAction, ) -> Result<()> { // the given profile image is expected to be already in the blob directory // as profile images can be set only by receiving messages, this should be always the case, however. - let mut contact = Contact::load_from_db(context, contact_id)?; + let mut contact = Contact::load_from_db(context, contact_id).await?; let changed = match profile_image { AvatarAction::Change(profile_image) => { contact.param.set(Param::ProfileImage, profile_image); @@ -1047,7 +1084,7 @@ pub(crate) fn set_profile_image( } }; if changed { - contact.update_param(context)?; + contact.update_param(context).await?; context.call_cb(Event::ContactsChanged(Some(contact_id))); } Ok(()) @@ -1119,8 +1156,8 @@ fn cat_fingerprint( impl Context { /// determine whether the specified addr maps to the/a self addr - pub fn is_self_addr(&self, addr: &str) -> Result { - let self_addr = match self.get_config(Config::ConfiguredAddr) { + pub async fn is_self_addr(&self, addr: &str) -> Result { + let self_addr = match self.get_config(Config::ConfiguredAddr).await { Some(s) => s, None => return Err(Error::NotConfigured), }; @@ -1222,11 +1259,11 @@ mod tests { #[async_std::test] async fn test_is_self_addr() -> Result<()> { let t = test_context(None); - assert!(t.ctx.is_self_addr("me@me.org").is_err()); + assert!(t.ctx.is_self_addr("me@me.org").await.is_err()); let addr = configure_alice_keypair(&t.ctx).await; - assert_eq!(t.ctx.is_self_addr("me@me.org")?, false); - assert_eq!(t.ctx.is_self_addr(&addr)?, true); + assert_eq!(t.ctx.is_self_addr("me@me.org").await?, false); + assert_eq!(t.ctx.is_self_addr(&addr).await?, true); Ok(()) } diff --git a/src/context.rs b/src/context.rs index 3f711fedf..e9790079a 100644 --- a/src/context.rs +++ b/src/context.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use std::ffi::OsString; use std::path::{Path, PathBuf}; -use std::sync::{Arc, Mutex, RwLock}; +use std::sync::{atomic::AtomicBool, Arc, Mutex, RwLock}; use crate::chat::*; use crate::config::Config; @@ -39,8 +39,8 @@ pub struct Context { /// Blob directory path blobdir: PathBuf, pub sql: Sql, - pub perform_inbox_jobs_needed: Arc>, - pub probe_imap_network: Arc>, + pub perform_inbox_jobs_needed: AtomicBool, + pub probe_imap_network: AtomicBool, pub inbox_thread: JobThread, pub sentbox_thread: JobThread, pub mvbox_thread: JobThread, @@ -81,7 +81,11 @@ pub fn get_info() -> HashMap<&'static str, String> { impl Context { /// Creates new context. - pub fn new(cb: Box, os_name: String, dbfile: PathBuf) -> Result { + pub async fn new( + cb: Box, + os_name: String, + dbfile: PathBuf, + ) -> Result { pretty_env_logger::try_init_timed().ok(); let mut blob_fname = OsString::new(); @@ -91,10 +95,10 @@ impl Context { if !blobdir.exists() { std::fs::create_dir_all(&blobdir)?; } - Context::with_blobdir(cb, os_name, dbfile, blobdir) + Context::with_blobdir(cb, os_name, dbfile, blobdir).await } - pub fn with_blobdir( + pub async fn with_blobdir( cb: Box, os_name: String, dbfile: PathBuf, @@ -120,14 +124,14 @@ impl Context { inbox_thread: JobThread::new("INBOX", "configured_inbox_folder", Imap::new()), sentbox_thread: JobThread::new("SENTBOX", "configured_sentbox_folder", Imap::new()), mvbox_thread: JobThread::new("MVBOX", "configured_mvbox_folder", Imap::new()), - probe_imap_network: Arc::new(RwLock::new(false)), - perform_inbox_jobs_needed: Arc::new(RwLock::new(false)), + probe_imap_network: Default::default(), + perform_inbox_jobs_needed: Default::default(), generating_key_mutex: Mutex::new(()), translated_stockstrings: RwLock::new(HashMap::new()), }; ensure!( - ctx.sql.open(&ctx, &ctx.dbfile, false), + ctx.sql.open(&ctx, &ctx.dbfile, false).await, "Failed opening sqlite database" ); @@ -208,57 +212,66 @@ impl Context { * UI chat/message related API ******************************************************************************/ - pub fn get_info(&self) -> HashMap<&'static str, String> { + pub async fn get_info(&self) -> HashMap<&'static str, String> { let unset = "0"; - let l = LoginParam::from_database(self, ""); - let l2 = LoginParam::from_database(self, "configured_"); - let displayname = self.get_config(Config::Displayname); - let chats = get_chat_cnt(self) as usize; + let l = LoginParam::from_database(self, "").await; + let l2 = LoginParam::from_database(self, "configured_").await; + let displayname = self.get_config(Config::Displayname).await; + let chats = get_chat_cnt(self).await as usize; let real_msgs = message::get_real_msg_cnt(self) as usize; let deaddrop_msgs = message::get_deaddrop_msg_cnt(self) as usize; - let contacts = Contact::get_real_cnt(self) as usize; - let is_configured = self.get_config_int(Config::Configured); + let contacts = Contact::get_real_cnt(self).await as usize; + let is_configured = self.get_config_int(Config::Configured).await; let dbversion = self .sql .get_raw_config_int(self, "dbversion") + .await .unwrap_or_default(); - let e2ee_enabled = self.get_config_int(Config::E2eeEnabled); - let mdns_enabled = self.get_config_int(Config::MdnsEnabled); - let bcc_self = self.get_config_int(Config::BccSelf); + let e2ee_enabled = self.get_config_int(Config::E2eeEnabled).await; + let mdns_enabled = self.get_config_int(Config::MdnsEnabled).await; + let bcc_self = self.get_config_int(Config::BccSelf).await; - let prv_key_cnt: Option = - self.sql - .query_get_value(self, "SELECT COUNT(*) FROM keypairs;", rusqlite::NO_PARAMS); + let prv_key_cnt: Option = self + .sql + .query_get_value(self, "SELECT COUNT(*) FROM keypairs;", rusqlite::NO_PARAMS) + .await; - let pub_key_cnt: Option = self.sql.query_get_value( - self, - "SELECT COUNT(*) FROM acpeerstates;", - rusqlite::NO_PARAMS, - ); + let pub_key_cnt: Option = self + .sql + .query_get_value( + self, + "SELECT COUNT(*) FROM acpeerstates;", + rusqlite::NO_PARAMS, + ) + .await; - let fingerprint_str = if let Some(key) = Key::from_self_public(self, &l2.addr, &self.sql) { - key.fingerprint() - } else { - "".into() - }; + let fingerprint_str = + if let Some(key) = Key::from_self_public(self, &l2.addr, &self.sql).await { + key.fingerprint() + } else { + "".into() + }; - let inbox_watch = self.get_config_int(Config::InboxWatch); - let sentbox_watch = self.get_config_int(Config::SentboxWatch); - let mvbox_watch = self.get_config_int(Config::MvboxWatch); - let mvbox_move = self.get_config_int(Config::MvboxMove); + let inbox_watch = self.get_config_int(Config::InboxWatch).await; + let sentbox_watch = self.get_config_int(Config::SentboxWatch).await; + let mvbox_watch = self.get_config_int(Config::MvboxWatch).await; + let mvbox_move = self.get_config_int(Config::MvboxMove).await; let folders_configured = self .sql .get_raw_config_int(self, "folders_configured") + .await .unwrap_or_default(); let configured_sentbox_folder = self .sql .get_raw_config(self, "configured_sentbox_folder") + .await .unwrap_or_else(|| "".to_string()); let configured_mvbox_folder = self .sql .get_raw_config(self, "configured_mvbox_folder") + .await .unwrap_or_else(|| "".to_string()); let mut res = get_info(); @@ -273,6 +286,7 @@ impl Context { res.insert( "selfavatar", self.get_config(Config::Selfavatar) + .await .unwrap_or_else(|| "".to_string()), ); res.insert("is_configured", is_configured.to_string()); @@ -301,7 +315,7 @@ impl Context { res } - pub fn get_fresh_msgs(&self) -> Vec { + pub async fn get_fresh_msgs(&self) -> Vec { let show_deaddrop = 0; self.sql .query_map( @@ -329,11 +343,12 @@ impl Context { Ok(ret) }, ) + .await .unwrap_or_default() } #[allow(non_snake_case)] - pub fn search_msgs(&self, chat_id: ChatId, query: impl AsRef) -> Vec { + pub async fn search_msgs(&self, chat_id: ChatId, query: impl AsRef) -> Vec { let real_query = query.as_ref().trim(); if real_query.is_empty() { return Vec::new(); @@ -383,6 +398,7 @@ impl Context { Ok(ret) }, ) + .await .unwrap_or_default() } @@ -390,8 +406,11 @@ impl Context { folder_name.as_ref() == "INBOX" } - pub fn is_sentbox(&self, folder_name: impl AsRef) -> bool { - let sentbox_name = self.sql.get_raw_config(self, "configured_sentbox_folder"); + pub async fn is_sentbox(&self, folder_name: impl AsRef) -> bool { + let sentbox_name = self + .sql + .get_raw_config(self, "configured_sentbox_folder") + .await; if let Some(name) = sentbox_name { name == folder_name.as_ref() } else { @@ -399,8 +418,11 @@ impl Context { } } - pub fn is_mvbox(&self, folder_name: impl AsRef) -> bool { - let mvbox_name = self.sql.get_raw_config(self, "configured_mvbox_folder"); + pub async fn is_mvbox(&self, folder_name: impl AsRef) -> bool { + let mvbox_name = self + .sql + .get_raw_config(self, "configured_mvbox_folder") + .await; if let Some(name) = mvbox_name { name == folder_name.as_ref() @@ -410,14 +432,14 @@ impl Context { } pub async fn do_heuristics_moves(&self, folder: &str, msg_id: MsgId) { - if !self.get_config_bool(Config::MvboxMove) { + if !self.get_config_bool(Config::MvboxMove).await { return; } - if self.is_mvbox(folder) { + if self.is_mvbox(folder).await { return; } - if let Ok(msg) = Message::load_from_db(self, msg_id) { + if let Ok(msg) = Message::load_from_db(self, msg_id).await { if msg.is_setupmessage() { // do not move setup messages; // there may be a non-delta device that wants to handle it @@ -567,11 +589,11 @@ mod tests { std::mem::drop(t.ctx); } - #[test] - fn test_get_info() { + #[async_std::test] + async fn test_get_info() { let t = dummy_context(); - let info = t.ctx.get_info(); + let info = t.ctx.get_info().await; assert!(info.get("database_dir").is_some()); } diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index 095dc359f..d8c7b3cfd 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -17,7 +17,6 @@ use crate::mimeparser::*; use crate::param::*; use crate::peerstate::*; use crate::securejoin::{self, handle_securejoin_handshake}; -use crate::sql; use crate::stock::StockMessage; use crate::{contact, location}; @@ -54,7 +53,7 @@ pub async fn dc_receive_imf( println!("{}", String::from_utf8_lossy(imf_raw)); } - let mut mime_parser = MimeMessage::from_bytes(context, imf_raw)?; + let mut mime_parser = MimeMessage::from_bytes(context, imf_raw).await?; // we can not add even an empty record if we have no info whatsoever ensure!(mime_parser.has_headers(), "No Headers Found"); @@ -183,7 +182,7 @@ pub async fn dc_receive_imf( } if let Some(avatar_action) = &mime_parser.user_avatar { - match contact::set_profile_image(&context, from_id, avatar_action) { + match contact::set_profile_image(&context, from_id, avatar_action).await { Ok(()) => { context.call_cb(Event::ChatModified(chat_id)); } @@ -317,7 +316,7 @@ async fn add_parts( // incoming non-chat messages may be discarded let mut allow_creation = true; let show_emails = - ShowEmails::from_i32(context.get_config_int(Config::ShowEmails)).unwrap_or_default(); + ShowEmails::from_i32(context.get_config_int(Config::ShowEmails).await).unwrap_or_default(); if mime_parser.is_system_message != SystemMessage::AutocryptSetupMessage && msgrmsg == MessengerMessage::No { @@ -377,7 +376,9 @@ async fn add_parts( } let (test_normal_chat_id, test_normal_chat_id_blocked) = - chat::lookup_by_contact_id(context, from_id).unwrap_or_default(); + chat::lookup_by_contact_id(context, from_id) + .await + .unwrap_or_default(); // get the chat_id - a chat_id here is no indicator that the chat is displayed in the normal list, // it might also be blocked and displayed in the deaddrop as a result @@ -399,7 +400,8 @@ async fn add_parts( create_blocked, from_id, to_ids, - )?; + ) + .await?; *chat_id = new_chat_id; chat_id_blocked = new_chat_id_blocked; if !chat_id.is_unset() @@ -433,6 +435,7 @@ async fn add_parts( } else if allow_creation { let (id, bl) = chat::create_or_lookup_by_contact_id(context, from_id, create_blocked) + .await .unwrap_or_default(); *chat_id = id; chat_id_blocked = bl; @@ -487,7 +490,8 @@ async fn add_parts( Blocked::Not, from_id, to_ids, - )?; + ) + .await?; *chat_id = new_chat_id; chat_id_blocked = new_chat_id_blocked; // automatically unblock chat when the user sends a message @@ -498,13 +502,14 @@ async fn add_parts( } if chat_id.is_unset() && allow_creation { let create_blocked = if MessengerMessage::No != msgrmsg - && !Contact::is_blocked_load(context, to_id) + && !Contact::is_blocked_load(context, to_id).await { Blocked::Not } else { Blocked::Deaddrop }; let (id, bl) = chat::create_or_lookup_by_contact_id(context, to_id, create_blocked) + .await .unwrap_or_default(); *chat_id = id; chat_id_blocked = bl; @@ -527,6 +532,7 @@ async fn add_parts( // maybe an Autocrypt Setup Message let (id, bl) = chat::create_or_lookup_by_contact_id(context, DC_CONTACT_ID_SELF, Blocked::Not) + .await .unwrap_or_default(); *chat_id = id; chat_id_blocked = bl; @@ -554,11 +560,11 @@ async fn add_parts( ); // unarchive chat - chat_id.unarchive(context)?; + chat_id.unarchive(context).await?; // if the mime-headers should be saved, find out its size // (the mime-header ends with an empty line) - let save_mime_headers = context.get_config_bool(Config::SaveMimeHeaders); + let save_mime_headers = context.get_config_bool(Config::SaveMimeHeaders).await; if let Some(raw) = mime_parser.get(HeaderDef::InReplyTo) { mime_in_reply_to = raw.clone(); } @@ -573,18 +579,21 @@ async fn add_parts( // (eg. one per attachment)) let icnt = mime_parser.parts.len(); - let mut txt_raw = None; + context + .sql + .with_conn(|mut conn| { + let subject = mime_parser.get_subject().unwrap_or_default(); + let mut txt_raw = None; - context.sql.prepare( - "INSERT INTO msgs \ + for part in mime_parser.parts.iter_mut() { + let mut stmt = conn.prepare_cached( + "INSERT INTO msgs \ (rfc724_mid, server_folder, server_uid, chat_id, from_id, to_id, timestamp, \ timestamp_sent, timestamp_rcvd, type, state, msgrmsg, txt, txt_raw, param, \ bytes, hidden, mime_headers, mime_in_reply_to, mime_references) \ VALUES (?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?);", - |mut stmt, conn| { - let subject = mime_parser.get_subject().unwrap_or_default(); + )?; - for part in mime_parser.parts.iter_mut() { if mime_parser.location_kml.is_some() && icnt == 1 && (part.msg == "-location-" || part.msg.is_empty()) @@ -633,14 +642,17 @@ async fn add_parts( ])?; txt_raw = None; + + // This is okay, as we use a cached prepared statement. + drop(stmt); let row_id = - sql::get_rowid_with_conn(context, conn, "msgs", "rfc724_mid", &rfc724_mid); + crate::sql::get_rowid(context, &mut conn, "msgs", "rfc724_mid", &rfc724_mid)?; *insert_msg_id = MsgId::new(row_id); created_db_entries.push((*chat_id, *insert_msg_id)); } Ok(()) - }, - )?; + }) + .await?; info!( context, @@ -763,7 +775,7 @@ fn calc_timestamps( /// /// on success the function returns the found/created (chat_id, chat_blocked) tuple . #[allow(non_snake_case, clippy::cognitive_complexity)] -fn create_or_lookup_group( +async fn create_or_lookup_group( context: &Context, mime_parser: &mut MimeMessage, allow_creation: bool, @@ -827,6 +839,7 @@ fn create_or_lookup_group( X_MrRemoveFromGrp = Some(optional_field); mime_parser.is_system_message = SystemMessage::MemberRemovedFromGroup; let left_group = Contact::lookup_id_by_addr(context, X_MrRemoveFromGrp.as_ref().unwrap()) + .await == from_id as u32; better_msg = context.stock_system_msg( if left_group { @@ -889,6 +902,7 @@ fn create_or_lookup_group( // check, if we have a chat with this group ID let (mut chat_id, chat_id_verified, _blocked) = chat::get_chat_id_by_grpid(context, &grpid) + .await .unwrap_or((ChatId::new(0), false, Blocked::Not)); if !chat_id.is_error() { if chat_id_verified { @@ -900,7 +914,7 @@ fn create_or_lookup_group( mime_parser.repl_msg_by_error(s); } } - if !chat::is_contact_in_chat(context, chat_id, from_id as u32) { + if !chat::is_contact_in_chat(context, chat_id, from_id as u32).await { // The From-address is not part of this group. // It could be a new user or a DSN from a mailer-daemon. // in any case we do not want to recreate the member list @@ -913,9 +927,12 @@ fn create_or_lookup_group( } // check if the group does not exist but should be created - let group_explicitly_left = chat::is_group_explicitly_left(context, &grpid).unwrap_or_default(); + let group_explicitly_left = chat::is_group_explicitly_left(context, &grpid) + .await + .unwrap_or_default(); let self_addr = context .get_config(Config::ConfiguredAddr) + .await .unwrap_or_default(); if chat_id.is_error() @@ -953,7 +970,8 @@ fn create_or_lookup_group( grpname.as_ref().unwrap(), create_blocked, create_verified, - ); + ) + .await; chat_id_blocked = create_blocked; recreate_member_list = true; } @@ -997,13 +1015,14 @@ fn create_or_lookup_group( if let Some(ref grpname) = grpname { if grpname.len() < 200 { info!(context, "updating grpname for chat {}", chat_id); - if sql::execute( - context, - &context.sql, - "UPDATE chats SET name=? WHERE id=?;", - params![grpname, chat_id], - ) - .is_ok() + if context + .sql + .execute( + "UPDATE chats SET name=? WHERE id=?;", + params![grpname, chat_id], + ) + .await + .is_ok() { context.call_cb(Event::ChatModified(chat_id)); } @@ -1012,7 +1031,7 @@ fn create_or_lookup_group( } if let Some(avatar_action) = &mime_parser.group_avatar { info!(context, "group-avatar change for {}", chat_id); - if let Ok(mut chat) = Chat::load_from_db(context, chat_id) { + if let Ok(mut chat) = Chat::load_from_db(context, chat_id).await { match avatar_action { AvatarAction::Change(profile_image) => { chat.param.set(Param::ProfileImage, profile_image); @@ -1021,33 +1040,33 @@ fn create_or_lookup_group( chat.param.remove(Param::ProfileImage); } }; - chat.update_param(context)?; + chat.update_param(context).await?; send_EVENT_CHAT_MODIFIED = true; } } // add members to group/check members if recreate_member_list { - if !chat::is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF) { - chat::add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF); + if !chat::is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF).await { + chat::add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF).await; } if from_id > DC_CONTACT_ID_LAST_SPECIAL - && !Contact::addr_equals_contact(context, &self_addr, from_id as u32) - && !chat::is_contact_in_chat(context, chat_id, from_id) + && !Contact::addr_equals_contact(context, &self_addr, from_id as u32).await + && !chat::is_contact_in_chat(context, chat_id, from_id).await { - chat::add_to_chat_contacts_table(context, chat_id, from_id as u32); + chat::add_to_chat_contacts_table(context, chat_id, from_id as u32).await; } for &to_id in to_ids.iter() { info!(context, "adding to={:?} to chat id={}", to_id, chat_id); - if !Contact::addr_equals_contact(context, &self_addr, to_id) - && !chat::is_contact_in_chat(context, chat_id, to_id) + if !Contact::addr_equals_contact(context, &self_addr, to_id).await + && !chat::is_contact_in_chat(context, chat_id, to_id).await { chat::add_to_chat_contacts_table(context, chat_id, to_id); } } send_EVENT_CHAT_MODIFIED = true; } else if let Some(removed_addr) = X_MrRemoveFromGrp { - let contact_id = Contact::lookup_id_by_addr(context, removed_addr); + let contact_id = Contact::lookup_id_by_addr(context, removed_addr).await; if contact_id != 0 { info!(context, "remove {:?} from chat id={}", contact_id, chat_id); chat::remove_from_chat_contacts_table(context, chat_id, contact_id); @@ -1178,16 +1197,14 @@ fn create_or_lookup_adhoc_group( Ok((new_chat_id, create_blocked)) } -fn create_group_record( +async fn create_group_record( context: &Context, grpid: impl AsRef, grpname: impl AsRef, create_blocked: Blocked, create_verified: VerifiedStatus, ) -> ChatId { - if sql::execute( - context, - &context.sql, + if context.sql.execute( "INSERT INTO chats (type, name, grpid, blocked, created_timestamp) VALUES(?, ?, ?, ?, ?);", params![ if VerifiedStatus::Unverified != create_verified { @@ -1200,7 +1217,7 @@ fn create_group_record( create_blocked, time(), ], - ) + ).await .is_err() { warn!( @@ -1211,7 +1228,12 @@ fn create_group_record( ); return ChatId::new(0); } - let row_id = sql::get_rowid(context, &context.sql, "chats", "grpid", grpid.as_ref()); + let row_id = context + .sql + .get_rowid(context, "chats", "grpid", grpid.as_ref()) + .await + .unwrap_or_default(); + let chat_id = ChatId::new(row_id); info!( context, diff --git a/src/e2ee.rs b/src/e2ee.rs index 940d0793b..317d1a542 100644 --- a/src/e2ee.rs +++ b/src/e2ee.rs @@ -27,18 +27,18 @@ pub struct EncryptHelper { } impl EncryptHelper { - pub fn new(context: &Context) -> Result { + pub async fn new(context: &Context) -> Result { let prefer_encrypt = - EncryptPreference::from_i32(context.get_config_int(Config::E2eeEnabled)) + EncryptPreference::from_i32(context.get_config_int(Config::E2eeEnabled).await) .unwrap_or_default(); - let addr = match context.get_config(Config::ConfiguredAddr) { + let addr = match context.get_config(Config::ConfiguredAddr).await { None => { bail!("addr not configured!"); } Some(addr) => addr, }; - let public_key = load_or_generate_self_public_key(context, &addr)?; + let public_key = load_or_generate_self_public_key(context, &addr).await?; Ok(EncryptHelper { prefer_encrypt, @@ -88,12 +88,12 @@ impl EncryptHelper { } /// Tries to encrypt the passed in `mail`. - pub fn encrypt( + pub async fn encrypt( &mut self, context: &Context, min_verified: PeerstateVerifiedStatus, mail_to_encrypt: lettre_email::PartBuilder, - peerstates: &[(Option, &str)], + peerstates: &[(Option>, &str)], ) -> Result { let mut keyring = Keyring::default(); @@ -109,6 +109,7 @@ impl EncryptHelper { let public_key = Key::from(self.public_key.clone()); keyring.add_ref(&public_key); let sign_key = Key::from_self_private(context, self.addr.clone(), &context.sql) + .await .ok_or_else(|| format_err!("missing own private key"))?; let raw_message = mail_to_encrypt.build().as_string().into_bytes(); @@ -119,7 +120,7 @@ impl EncryptHelper { } } -pub fn try_decrypt( +pub async fn try_decrypt( context: &Context, mail: &ParsedMail<'_>, message_time: i64, @@ -141,14 +142,14 @@ pub fn try_decrypt( if let Some(ref mut peerstate) = peerstate { if let Some(ref header) = autocryptheader { peerstate.apply_header(&header, message_time); - peerstate.save_to_db(&context.sql, false)?; + peerstate.save_to_db(&context.sql, false).await?; } else if message_time > peerstate.last_seen_autocrypt && !contains_report(mail) { peerstate.degrade_encryption(message_time); - peerstate.save_to_db(&context.sql, false)?; + peerstate.save_to_db(&context.sql, false).await?; } } else if let Some(ref header) = autocryptheader { let p = Peerstate::from_header(context, header, message_time); - p.save_to_db(&context.sql, true)?; + p.save_to_db(&context.sql, true).await?; peerstate = Some(p); } } @@ -158,16 +159,19 @@ pub fn try_decrypt( let mut public_keyring_for_validate = Keyring::default(); let mut out_mail = None; let mut signatures = HashSet::default(); - let self_addr = context.get_config(Config::ConfiguredAddr); + let self_addr = context.get_config(Config::ConfiguredAddr).await; if let Some(self_addr) = self_addr { - if private_keyring.load_self_private_for_decrypting(context, self_addr, &context.sql) { + if private_keyring + .load_self_private_for_decrypting(context, self_addr, &context.sql) + .await + { if peerstate.as_ref().map(|p| p.last_seen).unwrap_or_else(|| 0) == 0 { peerstate = Peerstate::from_addr(&context, &context.sql, &from); } if let Some(ref peerstate) = peerstate { if peerstate.degrade_event.is_some() { - handle_degrade_event(context, &peerstate)?; + handle_degrade_event(context, &peerstate).await?; } if let Some(ref key) = peerstate.gossip_key { public_keyring_for_validate.add_ref(key); @@ -195,18 +199,18 @@ pub fn try_decrypt( /// storing a new one when one doesn't exist yet. Care is taken to /// only generate one key per context even when multiple threads call /// this function concurrently. -fn load_or_generate_self_public_key( +async fn load_or_generate_self_public_key( context: &Context, self_addr: impl AsRef, ) -> Result { - if let Some(key) = Key::from_self_public(context, &self_addr, &context.sql) { + if let Some(key) = Key::from_self_public(context, &self_addr, &context.sql).await { return SignedPublicKey::try_from(key) .map_err(|_| Error::Message("Not a public key".into())); } let _guard = context.generating_key_mutex.lock().unwrap(); // Check again in case the key was generated while we were waiting for the lock. - if let Some(key) = Key::from_self_public(context, &self_addr, &context.sql) { + if let Some(key) = Key::from_self_public(context, &self_addr, &context.sql).await { return SignedPublicKey::try_from(key) .map_err(|_| Error::Message("Not a public key".into())); } @@ -214,10 +218,10 @@ fn load_or_generate_self_public_key( let start = std::time::Instant::now(); let keygen_type = - KeyGenType::from_i32(context.get_config_int(Config::KeyGenType)).unwrap_or_default(); + KeyGenType::from_i32(context.get_config_int(Config::KeyGenType).await).unwrap_or_default(); info!(context, "Generating keypair with type {}", keygen_type); let keypair = pgp::create_keypair(EmailAddress::new(self_addr.as_ref())?, keygen_type)?; - key::store_self_keypair(context, &keypair, KeyPairUse::Default)?; + key::store_self_keypair(context, &keypair, KeyPairUse::Default).await?; info!( context, "Keypair generated in {:.3}s.", @@ -347,14 +351,18 @@ fn contains_report(mail: &ParsedMail<'_>) -> bool { /// /// If this succeeds you are also guaranteed that the /// [Config::ConfiguredAddr] is configured, this address is returned. -pub fn ensure_secret_key_exists(context: &Context) -> Result { - let self_addr = context.get_config(Config::ConfiguredAddr).ok_or_else(|| { - format_err!(concat!( - "Failed to get self address, ", - "cannot ensure secret key if not configured." - )) - })?; - load_or_generate_self_public_key(context, &self_addr)?; +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." + )) + })?; + load_or_generate_self_public_key(context, &self_addr).await?; + Ok(self_addr) } @@ -371,7 +379,7 @@ mod tests { async fn test_prexisting() { let t = dummy_context(); let test_addr = configure_alice_keypair(&t.ctx).await; - assert_eq!(ensure_secret_key_exists(&t.ctx).unwrap(), test_addr); + assert_eq!(ensure_secret_key_exists(&t.ctx).await.unwrap(), test_addr); } #[test] @@ -412,7 +420,7 @@ Sent with my Delta Chat Messenger: https://delta.chat"; async fn test_existing() { let t = dummy_context(); let addr = configure_alice_keypair(&t.ctx).await; - let key = load_or_generate_self_public_key(&t.ctx, addr); + let key = load_or_generate_self_public_key(&t.ctx, addr).await; assert!(key.is_ok()); } diff --git a/src/imap/mod.rs b/src/imap/mod.rs index e520e55a7..9cb7018f8 100644 --- a/src/imap/mod.rs +++ b/src/imap/mod.rs @@ -367,11 +367,11 @@ impl Imap { if self.is_connected().await && !self.should_reconnect() { return Ok(()); } - if !context.sql.get_raw_config_bool(context, "configured") { + if !context.sql.get_raw_config_bool(context, "configured").await { return Err(Error::ConnectWithoutConfigure); } - let param = LoginParam::from_database(context, "configured_"); + let param = LoginParam::from_database(context, "configured_").await; // the trailing underscore is correct if self.connect(context, ¶m).await { @@ -415,7 +415,7 @@ impl Imap { let teardown = match &mut *self.session.lock().await { Some(ref mut session) => match session.capabilities().await { Ok(caps) => { - if !context.sql.is_open() { + if !context.sql.is_open().await { warn!(context, "IMAP-LOGIN as {} ok but ABORTING", lp.mail_user,); true } else { @@ -465,7 +465,7 @@ impl Imap { } pub async fn fetch(&self, context: &Context, watch_folder: &str) -> Result<()> { - if !context.sql.is_open() { + if !context.sql.is_open().await { // probably shutdown return Err(Error::InTeardown); } @@ -477,9 +477,13 @@ impl Imap { Ok(()) } - fn get_config_last_seen_uid>(&self, context: &Context, folder: S) -> (u32, u32) { + async fn get_config_last_seen_uid>( + &self, + context: &Context, + folder: S, + ) -> (u32, u32) { let key = format!("imap.mailbox.{}", folder.as_ref()); - if let Some(entry) = context.sql.get_raw_config(context, &key) { + if let Some(entry) = context.sql.get_raw_config(context, &key).await { // the entry has the format `imap.mailbox.=:` let mut parts = entry.split(':'); ( @@ -508,7 +512,7 @@ impl Imap { self.select_folder(context, Some(folder)).await?; // compare last seen UIDVALIDITY against the current one - let (uid_validity, last_seen_uid) = self.get_config_last_seen_uid(context, &folder); + let (uid_validity, last_seen_uid) = self.get_config_last_seen_uid(context, &folder).await; let config = self.config.read().await; let mailbox = config @@ -590,8 +594,8 @@ impl Imap { context: &Context, folder: S, ) -> Result { - let show_emails = - ShowEmails::from_i32(context.get_config_int(Config::ShowEmails)).unwrap_or_default(); + let show_emails = ShowEmails::from_i32(context.get_config_int(Config::ShowEmails).await) + .unwrap_or_default(); let (uid_validity, last_seen_uid) = self .select_with_uidvalidity(context, folder.as_ref()) @@ -706,7 +710,7 @@ impl Imap { Ok(read_cnt > 0) } - fn set_config_last_seen_uid>( + async fn set_config_last_seen_uid>( &self, context: &Context, folder: S, @@ -716,7 +720,11 @@ impl Imap { let key = format!("imap.mailbox.{}", folder.as_ref()); let val = format!("{}:{}", uidvalidity, lastseenuid); - context.sql.set_raw_config(context, &key, Some(&val)).ok(); + context + .sql + .set_raw_config(context, &key, Some(&val)) + .await + .ok(); } /// Fetches a single message by server UID. @@ -1080,7 +1088,8 @@ impl Imap { ) -> Result<()> { let folders_configured = context .sql - .get_raw_config_int(context, "folders_configured"); + .get_raw_config_int(context, "folders_configured") + .await; if folders_configured.unwrap_or_default() >= 3 { // the "3" here we increase if we have future updates to // to folder configuration @@ -1168,24 +1177,28 @@ impl Imap { } context .sql - .set_raw_config(context, "configured_inbox_folder", Some("INBOX"))?; + .set_raw_config(context, "configured_inbox_folder", Some("INBOX")) + .await?; if let Some(ref mvbox_folder) = mvbox_folder { - context.sql.set_raw_config( - context, - "configured_mvbox_folder", - Some(mvbox_folder), - )?; + context + .sql + .set_raw_config(context, "configured_mvbox_folder", Some(mvbox_folder)) + .await?; } if let Some(ref sentbox_folder) = sentbox_folder { - context.sql.set_raw_config( - context, - "configured_sentbox_folder", - Some(sentbox_folder.name()), - )?; + context + .sql + .set_raw_config( + context, + "configured_sentbox_folder", + Some(sentbox_folder.name()), + ) + .await?; } context .sql - .set_raw_config_int(context, "folders_configured", 3)?; + .set_raw_config_int(context, "folders_configured", 3) + .await?; } info!(context, "FINISHED configuring IMAP-folders."); Ok(()) diff --git a/src/imex.rs b/src/imex.rs index 8dcf408db..0084038bb 100644 --- a/src/imex.rs +++ b/src/imex.rs @@ -81,7 +81,7 @@ pub async fn imex(context: &Context, what: ImexMode, param1: Option) -> Result { +pub async fn has_backup(context: &Context, dir_name: impl AsRef) -> Result { let dir_name = dir_name.as_ref(); let dir_iter = std::fs::read_dir(dir_name)?; let mut newest_backup_time = 0; @@ -93,16 +93,17 @@ pub fn has_backup(context: &Context, dir_name: impl AsRef) -> Result newest_backup_time { newest_backup_path = Some(path); newest_backup_time = curr_backup_time; } info!(context, "backup_time of {} is {}", name, curr_backup_time); - sql.close(&context); + sql.close(&context).await; } } } @@ -125,7 +126,7 @@ async fn do_initiate_key_transfer(context: &Context) -> Result { let setup_code = create_setup_code(context); /* this may require a keypair to be created. this may take a second ... */ ensure!(!context.shall_stop_ongoing(), "canceled"); - let setup_file_content = render_setup_file(context, &setup_code)?; + let setup_file_content = render_setup_file(context, &setup_code).await?; /* encrypting may also take a while ... */ ensure!(!context.shall_stop_ongoing(), "canceled"); let setup_file_blob = BlobObject::create( @@ -134,7 +135,7 @@ async fn do_initiate_key_transfer(context: &Context) -> Result { setup_file_content.as_bytes(), )?; - let chat_id = chat::create_by_contact_id(context, DC_CONTACT_ID_SELF)?; + let chat_id = chat::create_by_contact_id(context, DC_CONTACT_ID_SELF).await?; msg = Message::default(); msg.viewtype = Viewtype::File; msg.param.set(Param::File, setup_file_blob.as_name()); @@ -152,7 +153,7 @@ async fn do_initiate_key_transfer(context: &Context) -> Result { info!(context, "Wait for setup message being sent ...",); while !context.shall_stop_ongoing() { std::thread::sleep(std::time::Duration::from_secs(1)); - if let Ok(msg) = Message::load_from_db(context, msg_id) { + if let Ok(msg) = Message::load_from_db(context, msg_id).await { if msg.is_sent() { info!(context, "... setup message sent.",); break; @@ -170,15 +171,16 @@ async fn do_initiate_key_transfer(context: &Context) -> Result { /// Renders HTML body of a setup file message. /// /// The `passphrase` must be at least 2 characters long. -pub fn render_setup_file(context: &Context, passphrase: &str) -> Result { +pub async fn render_setup_file(context: &Context, passphrase: &str) -> Result { ensure!( passphrase.len() >= 2, "Passphrase must be at least 2 chars long." ); - let self_addr = e2ee::ensure_secret_key_exists(context)?; + let self_addr = e2ee::ensure_secret_key_exists(context).await?; let private_key = Key::from_self_private(context, self_addr, &context.sql) + .await .ok_or_else(|| format_err!("Failed to get private key."))?; - let ac_headers = match context.get_config_bool(Config::E2eeEnabled) { + let ac_headers = match context.get_config_bool(Config::E2eeEnabled).await { false => None, true => Some(("Autocrypt-Prefer-Encrypt", "mutual")), }; @@ -239,8 +241,8 @@ pub fn create_setup_code(_context: &Context) -> String { ret } -fn maybe_add_bcc_self_device_msg(context: &Context) -> Result<()> { - if !context.sql.get_raw_config_bool(context, "bcc_self") { +async fn maybe_add_bcc_self_device_msg(context: &Context) -> Result<()> { + if !context.sql.get_raw_config_bool(context, "bcc_self").await { let mut msg = Message::new(Viewtype::Text); // TODO: define this as a stockstring once the wording is settled. msg.text = Some( @@ -249,15 +251,19 @@ fn maybe_add_bcc_self_device_msg(context: &Context) -> Result<()> { go to the settings and enable \"Send copy to self\"." .to_string(), ); - chat::add_device_msg(context, Some("bcc-self-hint"), Some(&mut msg))?; + chat::add_device_msg(context, Some("bcc-self-hint"), Some(&mut msg)).await?; } Ok(()) } -pub fn continue_key_transfer(context: &Context, msg_id: MsgId, setup_code: &str) -> Result<()> { +pub async fn continue_key_transfer( + context: &Context, + msg_id: MsgId, + setup_code: &str, +) -> Result<()> { ensure!(!msg_id.is_special(), "wrong id"); - let msg = Message::load_from_db(context, msg_id)?; + let msg = Message::load_from_db(context, msg_id).await?; ensure!( msg.is_setupmessage(), "Message is no Autocrypt Setup Message." @@ -267,8 +273,8 @@ pub fn continue_key_transfer(context: &Context, msg_id: MsgId, setup_code: &str) let file = dc_open_file(context, filename)?; let sc = normalize_setup_code(setup_code); let armored_key = decrypt_setup_file(context, &sc, file)?; - set_self_key(context, &armored_key, true, true)?; - maybe_add_bcc_self_device_msg(context)?; + set_self_key(context, &armored_key, true, true).await?; + maybe_add_bcc_self_device_msg(context).await?; Ok(()) } else { @@ -276,7 +282,7 @@ pub fn continue_key_transfer(context: &Context, msg_id: MsgId, setup_code: &str) } } -fn set_self_key( +async fn set_self_key( context: &Context, armored: &str, set_default: bool, @@ -301,7 +307,8 @@ fn set_self_key( }; context .sql - .set_raw_config_int(context, "e2ee_enabled", e2ee_enabled)?; + .set_raw_config_int(context, "e2ee_enabled", e2ee_enabled) + .await?; } None => { if prefer_encrypt_required { @@ -310,7 +317,7 @@ fn set_self_key( } }; - let self_addr = context.get_config(Config::ConfiguredAddr); + let self_addr = context.get_config(Config::ConfiguredAddr).await; ensure!(self_addr.is_some(), "Missing self addr"); let addr = EmailAddress::new(&self_addr.unwrap_or_default())?; @@ -331,7 +338,8 @@ fn set_self_key( } else { key::KeyPairUse::ReadOnly }, - )?; + ) + .await?; Ok(()) } @@ -359,8 +367,7 @@ pub fn normalize_setup_code(s: &str) -> String { out } -#[allow(non_snake_case)] -pub fn JobImexImap(context: &Context, job: &Job) -> Result<()> { +pub async fn job_imex_imap(context: &Context, job: &Job) -> Result<()> { ensure!(context.alloc_ongoing(), "could not allocate ongoing"); let what: Option = job.param.get_int(Param::Cmd).and_then(ImexMode::from_i32); let param = job.param.get(Param::Arg).unwrap_or_default(); @@ -369,10 +376,10 @@ pub fn JobImexImap(context: &Context, job: &Job) -> Result<()> { info!(context, "Import/export process started."); context.call_cb(Event::ImexProgress(10)); - ensure!(context.sql.is_open(), "Database not opened."); + ensure!(context.sql.is_open().await, "Database not opened."); if what == Some(ImexMode::ExportBackup) || what == Some(ImexMode::ExportSelfKeys) { // before we export anything, make sure the private key exists - if e2ee::ensure_secret_key_exists(context).is_err() { + if e2ee::ensure_secret_key_exists(context).await.is_err() { context.free_ongoing(); bail!("Cannot create private key or private key not available."); } else { @@ -381,10 +388,10 @@ pub fn JobImexImap(context: &Context, job: &Job) -> Result<()> { } let path = Path::new(param); let success = match what { - Some(ImexMode::ExportSelfKeys) => export_self_keys(context, path), - Some(ImexMode::ImportSelfKeys) => import_self_keys(context, path), - Some(ImexMode::ExportBackup) => export_backup(context, path), - Some(ImexMode::ImportBackup) => import_backup(context, path), + Some(ImexMode::ExportSelfKeys) => export_self_keys(context, path).await, + Some(ImexMode::ImportSelfKeys) => import_self_keys(context, path).await, + Some(ImexMode::ExportBackup) => export_backup(context, path).await, + Some(ImexMode::ImportBackup) => import_backup(context, path).await, None => { bail!("unknown IMEX type"); } @@ -404,7 +411,7 @@ pub fn JobImexImap(context: &Context, job: &Job) -> Result<()> { } /// Import Backup -fn import_backup(context: &Context, backup_to_import: impl AsRef) -> Result<()> { +async fn import_backup(context: &Context, backup_to_import: impl AsRef) -> Result<()> { info!( context, "Import \"{}\" to \"{}\".", @@ -413,7 +420,7 @@ fn import_backup(context: &Context, backup_to_import: impl AsRef) -> Resul ); ensure!( - !context.is_configured(), + !context.is_configured().await, "Cannot import backups to accounts in use." ); context.sql.close(&context); @@ -430,61 +437,71 @@ fn import_backup(context: &Context, backup_to_import: impl AsRef) -> Resul /* error already logged */ /* re-open copied database file */ ensure!( - context.sql.open(&context, &context.get_dbfile(), false), + context + .sql + .open(&context, &context.get_dbfile(), false) + .await, "could not re-open db" ); - delete_and_reset_all_device_msgs(&context)?; + delete_and_reset_all_device_msgs(&context).await?; let total_files_cnt = context .sql .query_get_value::<_, isize>(context, "SELECT COUNT(*) FROM backup_blobs;", params![]) + .await .unwrap_or_default() as usize; info!( context, "***IMPORT-in-progress: total_files_cnt={:?}", total_files_cnt, ); - let res = context.sql.query_map( - "SELECT file_name, file_content FROM backup_blobs ORDER BY id;", - params![], - |row| { - let name: String = row.get(0)?; - let blob: Vec = row.get(1)?; + let res = context + .sql + .query_map( + "SELECT file_name, file_content FROM backup_blobs ORDER BY id;", + params![], + |row| { + let name: String = row.get(0)?; + let blob: Vec = row.get(1)?; - Ok((name, blob)) - }, - |files| { - for (processed_files_cnt, file) in files.enumerate() { - let (file_name, file_blob) = file?; - if context.shall_stop_ongoing() { - return Ok(false); - } - let mut permille = processed_files_cnt * 1000 / total_files_cnt; - if permille < 10 { - permille = 10 - } - if permille > 990 { - permille = 990 - } - context.call_cb(Event::ImexProgress(permille)); - if file_blob.is_empty() { - continue; - } + Ok((name, blob)) + }, + |files| { + for (processed_files_cnt, file) in files.enumerate() { + let (file_name, file_blob) = file?; + if context.shall_stop_ongoing() { + return Ok(false); + } + let mut permille = processed_files_cnt * 1000 / total_files_cnt; + if permille < 10 { + permille = 10 + } + if permille > 990 { + permille = 990 + } + context.call_cb(Event::ImexProgress(permille)); + if file_blob.is_empty() { + continue; + } - let path_filename = context.get_blobdir().join(file_name); - dc_write_file(context, &path_filename, &file_blob)?; - } - Ok(true) - }, - ); + let path_filename = context.get_blobdir().join(file_name); + dc_write_file(context, &path_filename, &file_blob)?; + } + Ok(true) + }, + ) + .await; match res { Ok(all_files_extracted) => { if all_files_extracted { // only delete backup_blobs if all files were successfully extracted - sql::execute(context, &context.sql, "DROP TABLE backup_blobs;", params![])?; - sql::try_execute(context, &context.sql, "VACUUM;").ok(); + context + .sql + .execute("DROP TABLE backup_blobs;", params![]) + .await?; + context.sql.execute("VACUUM;", params![]).await.ok(); Ok(()) } else { bail!("received stop signal"); @@ -499,7 +516,7 @@ fn import_backup(context: &Context, backup_to_import: impl AsRef) -> Resul ******************************************************************************/ /* the FILE_PROGRESS macro calls the callback with the permille of files processed. The macro avoids weird values of 0% or 100% while still working. */ -fn export_backup(context: &Context, dir: impl AsRef) -> Result<()> { +async fn export_backup(context: &Context, dir: impl AsRef) -> Result<()> { // get a fine backup file name (the name includes the date so that multiple backup instances are possible) // FIXME: we should write to a temporary file first and rename it on success. this would guarantee the backup is complete. // let dest_path_filename = dc_get_next_backup_file(context, dir, res); @@ -507,9 +524,9 @@ fn export_backup(context: &Context, dir: impl AsRef) -> Result<()> { let dest_path_filename = dc_get_next_backup_path(dir, now)?; let dest_path_string = dest_path_filename.to_string_lossy().to_string(); - sql::housekeeping(context); + sql::housekeeping(context).await; - sql::try_execute(context, &context.sql, "VACUUM;").ok(); + context.sql.execute("VACUUM;", params![]).await.ok(); // we close the database during the copy of the dbfile context.sql.close(context); @@ -531,18 +548,20 @@ fn export_backup(context: &Context, dir: impl AsRef) -> Result<()> { } let dest_sql = Sql::new(); ensure!( - dest_sql.open(context, &dest_path_filename, false), + dest_sql.open(context, &dest_path_filename, false).await, "could not open exported database {}", dest_path_string ); - let res = match add_files_to_export(context, &dest_sql) { + let res = match add_files_to_export(context, &dest_sql).await { Err(err) => { dc_delete_file(context, &dest_path_filename); error!(context, "backup failed: {}", err); Err(err) } Ok(()) => { - dest_sql.set_raw_config_int(context, "backup_time", now as i32)?; + dest_sql + .set_raw_config_int(context, "backup_time", now as i32) + .await?; context.call_cb(Event::ImexFileWritten(dest_path_filename)); Ok(()) } @@ -552,16 +571,15 @@ fn export_backup(context: &Context, dir: impl AsRef) -> Result<()> { Ok(res?) } -fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> { +async fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> { // add all files as blobs to the database copy (this does not require // the source to be locked, neigher the destination as it is used only here) - if !sql.table_exists("backup_blobs") { - sql::execute( - context, - &sql, + if !sql.table_exists("backup_blobs").await? { + sql.execute( "CREATE TABLE backup_blobs (id INTEGER PRIMARY KEY, file_name, file_content);", params![], - )? + ) + .await?; } // copy all files from BLOBDIR into backup-db let mut total_files_cnt = 0; @@ -570,47 +588,50 @@ fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> { total_files_cnt += dir_handle.filter(|r| r.is_ok()).count(); info!(context, "EXPORT: total_files_cnt={}", total_files_cnt); - // scan directory, pass 2: copy files - let dir_handle = std::fs::read_dir(&dir)?; - let exported_all_files = sql.prepare( - "INSERT INTO backup_blobs (file_name, file_content) VALUES (?, ?);", - |mut stmt, _| { - let mut processed_files_cnt = 0; - for entry in dir_handle { - let entry = entry?; - if context.shall_stop_ongoing() { - return Ok(false); - } - processed_files_cnt += 1; - let permille = max(min(processed_files_cnt * 1000 / total_files_cnt, 990), 10); - context.call_cb(Event::ImexProgress(permille)); - let name_f = entry.file_name(); - let name = name_f.to_string_lossy(); - if name.starts_with("delta-chat") && name.ends_with(".bak") { + sql.with_conn(|conn| { + // scan directory, pass 2: copy files + let dir_handle = std::fs::read_dir(&dir)?; + + let mut stmt = conn + .prepare_cached("INSERT INTO backup_blobs (file_name, file_content) VALUES (?, ?);")?; + + let mut processed_files_cnt = 0; + for entry in dir_handle { + let entry = entry?; + if context.shall_stop_ongoing() { + return Ok(()); + } + processed_files_cnt += 1; + let permille = max(min(processed_files_cnt * 1000 / total_files_cnt, 990), 10); + context.call_cb(Event::ImexProgress(permille)); + + let name_f = entry.file_name(); + let name = name_f.to_string_lossy(); + if name.starts_with("delta-chat") && name.ends_with(".bak") { + continue; + } + info!(context, "EXPORT: copying filename={}", name); + let curr_path_filename = context.get_blobdir().join(entry.file_name()); + if let Ok(buf) = dc_read_file(context, &curr_path_filename) { + if buf.is_empty() { continue; } - info!(context, "EXPORT: copying filename={}", name); - let curr_path_filename = context.get_blobdir().join(entry.file_name()); - if let Ok(buf) = dc_read_file(context, &curr_path_filename) { - if buf.is_empty() { - continue; - } - // bail out if we can't insert - stmt.execute(params![name, buf])?; - } + // bail out if we can't insert + stmt.execute(params![name, buf])?; } - Ok(true) - }, - )?; - ensure!(exported_all_files, "canceled during export-files"); + } + Ok(()) + }) + .await?; + Ok(()) } /******************************************************************************* * Classic key import ******************************************************************************/ -fn import_self_keys(context: &Context, dir: impl AsRef) -> Result<()> { +async fn import_self_keys(context: &Context, dir: impl AsRef) -> Result<()> { /* hint: even if we switch to import Autocrypt Setup Files, we should leave the possibility to import plain ASC keys, at least keys without a password, if we do not want to implement a password entry function. Importing ASC keys is useful to use keys in Delta Chat used by any other non-Autocrypt-PGP implementation. @@ -645,7 +666,7 @@ fn import_self_keys(context: &Context, dir: impl AsRef) -> Result<()> { match dc_read_file(context, &path_plus_name) { Ok(buf) => { let armored = std::string::String::from_utf8_lossy(&buf); - if let Err(err) = set_self_key(context, &armored, set_default, false) { + if let Err(err) = set_self_key(context, &armored, set_default, false).await { error!(context, "set_self_key: {}", err); continue; } @@ -662,45 +683,48 @@ fn import_self_keys(context: &Context, dir: impl AsRef) -> Result<()> { Ok(()) } -fn export_self_keys(context: &Context, dir: impl AsRef) -> Result<()> { +async fn export_self_keys(context: &Context, dir: impl AsRef) -> Result<()> { let mut export_errors = 0; - context.sql.query_map( - "SELECT id, public_key, private_key, is_default FROM keypairs;", - params![], - |row| { - let id = row.get(0)?; - let public_key_blob: Vec = row.get(1)?; - let public_key = Key::from_slice(&public_key_blob, KeyType::Public); - let private_key_blob: Vec = row.get(2)?; - let private_key = Key::from_slice(&private_key_blob, KeyType::Private); - let is_default: i32 = row.get(3)?; + context + .sql + .query_map( + "SELECT id, public_key, private_key, is_default FROM keypairs;", + params![], + |row| { + let id = row.get(0)?; + let public_key_blob: Vec = row.get(1)?; + let public_key = Key::from_slice(&public_key_blob, KeyType::Public); + let private_key_blob: Vec = row.get(2)?; + let private_key = Key::from_slice(&private_key_blob, KeyType::Private); + let is_default: i32 = row.get(3)?; - Ok((id, public_key, private_key, is_default)) - }, - |keys| { - for key_pair in keys { - let (id, public_key, private_key, is_default) = key_pair?; - let id = Some(id).filter(|_| is_default != 0); - if let Some(key) = public_key { - if export_key_to_asc_file(context, &dir, id, &key).is_err() { + Ok((id, public_key, private_key, is_default)) + }, + |keys| { + for key_pair in keys { + let (id, public_key, private_key, is_default) = key_pair?; + let id = Some(id).filter(|_| is_default != 0); + if let Some(key) = public_key { + if export_key_to_asc_file(context, &dir, id, &key).is_err() { + export_errors += 1; + } + } else { export_errors += 1; } - } else { - export_errors += 1; - } - if let Some(key) = private_key { - if export_key_to_asc_file(context, &dir, id, &key).is_err() { + if let Some(key) = private_key { + if export_key_to_asc_file(context, &dir, id, &key).is_err() { + export_errors += 1; + } + } else { export_errors += 1; } - } else { - export_errors += 1; } - } - Ok(()) - }, - )?; + Ok(()) + }, + ) + .await?; ensure!(export_errors == 0, "errors while exporting keys"); Ok(()) @@ -745,7 +769,7 @@ mod tests { let t = test_context(Some(Box::new(logging_cb))); configure_alice_keypair(&t.ctx).await; - let msg = render_setup_file(&t.ctx, "hello").unwrap(); + let msg = render_setup_file(&t.ctx, "hello").await.unwrap(); println!("{}", &msg); // Check some substrings, indicating things got substituted. // In particular note the mixing of `\r\n` and `\n` depending @@ -767,7 +791,7 @@ mod tests { .set_stock_translation(StockMessage::AcSetupMsgBody, "hello\r\nthere".to_string()) .unwrap(); configure_alice_keypair(&t.ctx).await; - let msg = render_setup_file(&t.ctx, "pw").unwrap(); + let msg = render_setup_file(&t.ctx, "pw").await.unwrap(); println!("{}", &msg); assert!(msg.contains("

hello
there

")); } diff --git a/src/job.rs b/src/job.rs index f7b13557f..19ebc75e0 100644 --- a/src/job.rs +++ b/src/job.rs @@ -150,6 +150,7 @@ impl Job { context .sql .execute("DELETE FROM jobs WHERE id=?;", params![self.job_id as i32]) + .await .is_ok() } @@ -157,18 +158,19 @@ impl Job { /// /// To add a new job, use [job_add]. async fn update(&self, context: &Context) -> bool { - sql::execute( - context, - &context.sql, - "UPDATE jobs SET desired_timestamp=?, tries=?, param=? WHERE id=?;", - params![ - self.desired_timestamp, - self.tries as i64, - self.param.to_string(), - self.job_id as i32, - ], - ) - .is_ok() + context + .sql + .execute( + "UPDATE jobs SET desired_timestamp=?, tries=?, param=? WHERE id=?;", + params![ + self.desired_timestamp, + self.tries as i64, + self.param.to_string(), + self.job_id as i32, + ], + ) + .await + .is_ok() } async fn smtp_send( @@ -247,7 +249,7 @@ impl Job { async fn send_msg_to_smtp(&mut self, context: &Context) -> Status { // connect to SMTP server, if not yet done if !context.smtp.is_connected().await { - let loginparam = LoginParam::from_database(context, "configured_"); + let loginparam = LoginParam::from_database(context, "configured_").await; if let Err(err) = context.smtp.connect(context, &loginparam).await { warn!(context, "SMTP connection failure: {:?}", err); return Status::RetryLater; @@ -310,32 +312,35 @@ impl Job { contact_id: u32, ) -> sql::Result<(Vec, Vec)> { // Extract message IDs from job parameters - let res: Vec<(u32, MsgId)> = context.sql.query_map( - "SELECT id, param FROM jobs WHERE foreign_id=? AND id!=?", - params![contact_id, self.job_id], - |row| { - let job_id: u32 = row.get(0)?; - let params_str: String = row.get(1)?; - let params: Params = params_str.parse().unwrap_or_default(); - Ok((job_id, params)) - }, - |jobs| { - let res = jobs - .filter_map(|row| { - let (job_id, params) = row.ok()?; - let msg_id = params.get_msg_id()?; - Some((job_id, msg_id)) - }) - .collect(); - Ok(res) - }, - )?; + let res: Vec<(u32, MsgId)> = context + .sql + .query_map( + "SELECT id, param FROM jobs WHERE foreign_id=? AND id!=?", + params![contact_id, self.job_id], + |row| { + let job_id: u32 = row.get(0)?; + let params_str: String = row.get(1)?; + let params: Params = params_str.parse().unwrap_or_default(); + Ok((job_id, params)) + }, + |jobs| { + let res = jobs + .filter_map(|row| { + let (job_id, params) = row.ok()?; + let msg_id = params.get_msg_id()?; + Some((job_id, msg_id)) + }) + .collect(); + Ok(res) + }, + ) + .await?; // Load corresponding RFC724 message IDs let mut job_ids = Vec::new(); let mut rfc724_mids = Vec::new(); for (job_id, msg_id) in res { - if let Ok(Message { rfc724_mid, .. }) = Message::load_from_db(context, msg_id) { + if let Ok(Message { rfc724_mid, .. }) = Message::load_from_db(context, msg_id).await { job_ids.push(job_id); rfc724_mids.push(rfc724_mid); } @@ -344,14 +349,14 @@ impl Job { } async fn send_mdn(&mut self, context: &Context) -> Status { - if !context.get_config_bool(Config::MdnsEnabled) { + if !context.get_config_bool(Config::MdnsEnabled).await { // User has disabled MDNs after job scheduling but before // execution. return Status::Finished(Err(format_err!("MDNs are disabled"))); } let contact_id = self.foreign_id; - let contact = job_try!(Contact::load_from_db(context, contact_id)); + let contact = job_try!(Contact::load_from_db(context, contact_id).await); if contact.is_blocked() { return Status::Finished(Err(format_err!("Contact is blocked"))); } @@ -379,7 +384,7 @@ impl Job { ) } - let msg = job_try!(Message::load_from_db(context, msg_id)); + let msg = job_try!(Message::load_from_db(context, msg_id).await); let mimefactory = job_try!(MimeFactory::from_mdn(context, &msg, additional_rfc724_mids)); let rendered_msg = job_try!(mimefactory.render()); let body = rendered_msg.message; @@ -391,7 +396,7 @@ impl Job { // connect to SMTP server, if not yet done if !context.smtp.is_connected().await { - let loginparam = LoginParam::from_database(context, "configured_"); + let loginparam = LoginParam::from_database(context, "configured_").await; if let Err(err) = context.smtp.connect(context, &loginparam).await { warn!(context, "SMTP connection failure: {:?}", err); return Status::RetryLater; @@ -411,7 +416,7 @@ impl Job { async fn move_msg(&mut self, context: &Context) -> Status { let imap_inbox = &context.inbox_thread.imap; - let msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id))); + let msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)).await); if let Err(err) = imap_inbox.ensure_configured_folders(context, true).await { warn!(context, "could not configure folders: {:?}", err); @@ -419,7 +424,8 @@ impl Job { } let dest_folder = context .sql - .get_raw_config(context, "configured_mvbox_folder"); + .get_raw_config(context, "configured_mvbox_folder") + .await; if let Some(dest_folder) = dest_folder { let server_folder = msg.server_folder.as_ref().unwrap(); @@ -453,7 +459,7 @@ impl Job { async fn delete_msg_on_imap(&mut self, context: &Context) -> Status { let imap_inbox = &context.inbox_thread.imap; - let mut msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id))); + let mut msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)).await); if !msg.rfc724_mid.is_empty() { if message::rfc724_mid_cnt(context, &msg.rfc724_mid) > 1 { @@ -488,6 +494,7 @@ impl Job { if let Some(mvbox_folder) = context .sql .get_raw_config(context, "configured_mvbox_folder") + .await { imap_inbox.empty_folder(context, &mvbox_folder).await; } @@ -501,7 +508,7 @@ impl Job { async fn markseen_msg_on_imap(&mut self, context: &Context) -> Status { let imap_inbox = &context.inbox_thread.imap; - let msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id))); + let msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)).await); let folder = msg.server_folder.as_ref().unwrap(); match imap_inbox.set_seen(context, folder, msg.server_uid).await { @@ -513,7 +520,7 @@ impl Job { // The job will not be retried so locally // there is no risk of double-sending MDNs. if msg.param.get_bool(Param::WantsMdn).unwrap_or_default() - && context.get_config_bool(Config::MdnsEnabled) + && context.get_config_bool(Config::MdnsEnabled).await { if let Err(err) = send_mdn(context, &msg).await { warn!(context, "could not send out mdn for {}: {}", msg.id, err); @@ -543,7 +550,8 @@ impl Job { } let dest_folder = context .sql - .get_raw_config(context, "configured_mvbox_folder"); + .get_raw_config(context, "configured_mvbox_folder") + .await; if let Some(dest_folder) = dest_folder { let mut dest_uid = 0; if ImapActionResult::RetryLater @@ -566,67 +574,70 @@ impl Job { /// Delete all pending jobs with the given action. pub async fn kill_action(context: &Context, action: Action) -> bool { - sql::execute( - context, - &context.sql, - "DELETE FROM jobs WHERE action=?;", - params![action], - ) - .is_ok() + context + .sql + .execute("DELETE FROM jobs WHERE action=?;", params![action]) + .await + .is_ok() } /// Remove jobs with specified IDs. pub async fn kill_ids(context: &Context, job_ids: &[u32]) -> sql::Result<()> { - sql::execute( - context, - &context.sql, - format!( - "DELETE FROM jobs WHERE id IN({})", - job_ids.iter().map(|_| "?").join(",") - ), - job_ids, - ) + context + .sql + .execute( + format!( + "DELETE FROM jobs WHERE id IN({})", + job_ids.iter().map(|_| "?").join(",") + ), + job_ids, + ) + .await?; + Ok(()) } pub async fn perform_inbox_fetch(context: &Context) { - let use_network = context.get_config_bool(Config::InboxWatch); + let use_network = context.get_config_bool(Config::InboxWatch).await; context.inbox_thread.fetch(context, use_network).await; } pub async fn perform_mvbox_fetch(context: &Context) { - let use_network = context.get_config_bool(Config::MvboxWatch); + let use_network = context.get_config_bool(Config::MvboxWatch).await; context.mvbox_thread.fetch(context, use_network).await; } pub async fn perform_sentbox_fetch(context: &Context) { - let use_network = context.get_config_bool(Config::SentboxWatch); + let use_network = context.get_config_bool(Config::SentboxWatch).await; context.sentbox_thread.fetch(context, use_network).await; } pub async fn perform_inbox_idle(context: &Context) { - if *context.perform_inbox_jobs_needed.clone().read().unwrap() { + if context + .perform_inbox_jobs_needed + .load(std::sync::atomic::Ordering::Relaxed) + { info!( context, "INBOX-IDLE will not be started because of waiting jobs." ); return; } - let use_network = context.get_config_bool(Config::InboxWatch); + let use_network = context.get_config_bool(Config::InboxWatch).await; context.inbox_thread.idle(context, use_network).await; } pub async fn perform_mvbox_idle(context: &Context) { - let use_network = context.get_config_bool(Config::MvboxWatch); + let use_network = context.get_config_bool(Config::MvboxWatch).await; context.mvbox_thread.idle(context, use_network).await; } pub async fn perform_sentbox_idle(context: &Context) { - let use_network = context.get_config_bool(Config::SentboxWatch); + let use_network = context.get_config_bool(Config::SentboxWatch).await; context.sentbox_thread.idle(context, use_network).await; } @@ -638,7 +649,9 @@ pub async fn interrupt_inbox_idle(context: &Context) { // If it's currently fetching then we can not get the lock // but we flag it for checking jobs so that idle will be skipped. if !context.inbox_thread.try_interrupt_idle(context).await { - *context.perform_inbox_jobs_needed.write().unwrap() = true; + context + .perform_inbox_jobs_needed + .store(true, std::sync::atomic::Ordering::Relaxed); warn!(context, "could not interrupt idle"); } } @@ -687,7 +700,7 @@ pub async fn perform_smtp_idle(context: &Context) { ); } PerformJobsNeeded::Not | PerformJobsNeeded::AvoidDos => { - let dur = get_next_wakeup_time(context, Thread::Smtp); + let dur = get_next_wakeup_time(context, Thread::Smtp).await; context.smtp.notify_receiver.recv().timeout(dur).await.ok(); } @@ -696,7 +709,7 @@ pub async fn perform_smtp_idle(context: &Context) { info!(context, "SMTP-idle ended.",); } -fn get_next_wakeup_time(context: &Context, thread: Thread) -> time::Duration { +async fn get_next_wakeup_time(context: &Context, thread: Thread) -> time::Duration { let t: i64 = context .sql .query_get_value( @@ -704,6 +717,7 @@ fn get_next_wakeup_time(context: &Context, thread: Thread) -> time::Duration { "SELECT MIN(desired_timestamp) FROM jobs WHERE thread=?;", params![thread], ) + .await .unwrap_or_default(); let mut wakeup_time = time::Duration::new(10 * 60, 0); @@ -722,7 +736,9 @@ fn get_next_wakeup_time(context: &Context, thread: Thread) -> time::Duration { pub async fn maybe_network(context: &Context) { { context.smtp.state.write().await.probe_network = true; - *context.probe_imap_network.write().unwrap() = true; + context + .probe_imap_network + .store(true, std::sync::atomic::Ordering::Relaxed); } interrupt_smtp_idle(context).await; @@ -731,14 +747,15 @@ pub async fn maybe_network(context: &Context) { interrupt_sentbox_idle(context).await; } -pub fn action_exists(context: &Context, action: Action) -> bool { +pub async fn action_exists(context: &Context, action: Action) -> bool { context .sql .exists("SELECT id FROM jobs WHERE action=?;", params![action]) + .await .unwrap_or_default() } -fn set_delivered(context: &Context, msg_id: MsgId) { +async fn set_delivered(context: &Context, msg_id: MsgId) { message::update_msg_state(context, msg_id, MessageState::OutDelivered); let chat_id: ChatId = context .sql @@ -747,19 +764,20 @@ fn set_delivered(context: &Context, msg_id: MsgId) { "SELECT chat_id FROM msgs WHERE id=?", params![msg_id], ) + .await .unwrap_or_default(); context.call_cb(Event::MsgDelivered { chat_id, msg_id }); } // special case for DC_JOB_SEND_MSG_TO_SMTP pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> { - let mut msg = Message::load_from_db(context, msg_id)?; + let mut msg = Message::load_from_db(context, msg_id).await?; msg.try_calc_and_set_dimensions(context).ok(); /* create message */ let needs_encryption = msg.param.get_bool(Param::GuaranteeE2ee).unwrap_or_default(); - let attach_selfavatar = match chat::shall_attach_selfavatar(context, msg.chat_id) { + let attach_selfavatar = match chat::shall_attach_selfavatar(context, msg.chat_id).await { Ok(attach_selfavatar) => attach_selfavatar, Err(err) => { warn!(context, "job: cannot get selfavatar-state: {}", err); @@ -773,9 +791,10 @@ pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> { let from = context .get_config(Config::ConfiguredAddr) + .await .unwrap_or_default(); let lowercase_from = from.to_lowercase(); - if context.get_config_bool(Config::BccSelf) + if context.get_config_bool(Config::BccSelf).await && !recipients .iter() .any(|x| x.to_lowercase() == lowercase_from) @@ -813,16 +832,17 @@ pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> { } if rendered_msg.is_gossiped { - chat::set_gossiped_timestamp(context, msg.chat_id, time())?; + chat::set_gossiped_timestamp(context, msg.chat_id, time()).await?; } if 0 != rendered_msg.last_added_location_id { - if let Err(err) = location::set_kml_sent_timestamp(context, msg.chat_id, time()) { + if let Err(err) = location::set_kml_sent_timestamp(context, msg.chat_id, time()).await { error!(context, "Failed to set kml sent_timestamp: {:?}", err); } if !msg.hidden { if let Err(err) = location::set_msg_location_id(context, msg.id, rendered_msg.last_added_location_id) + .await { error!(context, "Failed to set msg_location_id: {:?}", err); } @@ -830,7 +850,7 @@ pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> { } if attach_selfavatar { - if let Err(err) = msg.chat_id.set_selfavatar_timestamp(context, time()) { + if let Err(err) = msg.chat_id.set_selfavatar_timestamp(context, time()).await { error!(context, "Failed to set selfavatar timestamp: {:?}", err); } } @@ -855,9 +875,15 @@ pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> { pub async fn perform_inbox_jobs(context: &Context) { info!(context, "dc_perform_inbox_jobs starting.",); - let probe_imap_network = *context.probe_imap_network.clone().read().unwrap(); - *context.probe_imap_network.write().unwrap() = false; - *context.perform_inbox_jobs_needed.write().unwrap() = false; + let probe_imap_network = context + .probe_imap_network + .load(std::sync::atomic::Ordering::Relaxed); + context + .probe_imap_network + .store(false, std::sync::atomic::Ordering::Relaxed); + context + .perform_inbox_jobs_needed + .store(false, std::sync::atomic::Ordering::Relaxed); job_perform(context, Thread::Imap, probe_imap_network).await; info!(context, "dc_perform_inbox_jobs ended.",); @@ -872,7 +898,7 @@ pub async fn perform_sentbox_jobs(context: &Context) { } async fn job_perform(context: &Context, thread: Thread, probe_network: bool) { - while let Some(mut job) = load_next_job(context, thread, probe_network) { + while let Some(mut job) = load_next_job(context, thread, probe_network).await { info!(context, "{}-job {} started...", thread, job); // some configuration jobs are "exclusive": @@ -891,77 +917,77 @@ async fn job_perform(context: &Context, thread: Thread, probe_network: bool) { x => x, }; - if Action::ConfigureImap == job.action || Action::ImexImap == job.action { - context.sentbox_thread.unsuspend(context).await; - context.mvbox_thread.unsuspend(context).await; - suspend_smtp_thread(context, false).await; - break; - } + // if Action::ConfigureImap == job.action || Action::ImexImap == job.action { + // context.sentbox_thread.unsuspend(context).await; + // context.mvbox_thread.unsuspend(context).await; + // suspend_smtp_thread(context, false).await; + // break; + // } - match try_res { - Status::RetryNow | Status::RetryLater => { - let tries = job.tries + 1; + // match try_res { + // Status::RetryNow | Status::RetryLater => { + // let tries = job.tries + 1; - if tries < JOB_RETRIES { - info!( - context, - "{} thread increases job {} tries to {}", thread, job, tries - ); - job.tries = tries; - let time_offset = get_backoff_time_offset(tries); - job.desired_timestamp = time() + time_offset; - job.update(context).await; - info!( - context, - "{}-job #{} not succeeded on try #{}, retry in {} seconds.", - thread, - job.job_id as u32, - tries, - time_offset - ); - if thread == Thread::Smtp && tries < JOB_RETRIES - 1 { - context.smtp.state.write().await.perform_jobs_needed = - PerformJobsNeeded::AvoidDos; - } - } else { - info!( - context, - "{} thread removes job {} as it exhausted {} retries", - thread, - job, - JOB_RETRIES - ); - if job.action == Action::SendMsgToSmtp { - message::set_msg_failed( - context, - MsgId::new(job.foreign_id), - job.pending_error.as_ref(), - ); - } - job.delete(context).await; - } - if !probe_network { - continue; - } - // on dc_maybe_network() we stop trying here; - // these jobs are already tried once. - // otherwise, we just continue with the next job - // to give other jobs a chance being tried at least once. - break; - } - Status::Finished(res) => { - if let Err(err) = res { - warn!( - context, - "{} removes job {} as it failed with error {:?}", thread, job, err - ); - } else { - info!(context, "{} removes job {} as it succeeded", thread, job); - } + // if tries < JOB_RETRIES { + // info!( + // context, + // "{} thread increases job {} tries to {}", thread, job, tries + // ); + // job.tries = tries; + // let time_offset = get_backoff_time_offset(tries); + // job.desired_timestamp = time() + time_offset; + // job.update(context).await; + // info!( + // context, + // "{}-job #{} not succeeded on try #{}, retry in {} seconds.", + // thread, + // job.job_id as u32, + // tries, + // time_offset + // ); + // if thread == Thread::Smtp && tries < JOB_RETRIES - 1 { + // context.smtp.state.write().await.perform_jobs_needed = + // PerformJobsNeeded::AvoidDos; + // } + // } else { + // info!( + // context, + // "{} thread removes job {} as it exhausted {} retries", + // thread, + // job, + // JOB_RETRIES + // ); + // if job.action == Action::SendMsgToSmtp { + // message::set_msg_failed( + // context, + // MsgId::new(job.foreign_id), + // job.pending_error.as_ref(), + // ); + // } + // job.delete(context).await; + // } + // if !probe_network { + // continue; + // } + // // on dc_maybe_network() we stop trying here; + // // these jobs are already tried once. + // // otherwise, we just continue with the next job + // // to give other jobs a chance being tried at least once. + // break; + // } + // Status::Finished(res) => { + // if let Err(err) = res { + // warn!( + // context, + // "{} removes job {} as it failed with error {:?}", thread, job, err + // ); + // } else { + // info!(context, "{} removes job {} as it succeeded", thread, job); + // } - job.delete(context).await; - } - } + // job.delete(context).await; + // } + // } } } @@ -986,7 +1012,7 @@ async fn perform_job_action( Action::MoveMsg => job.move_msg(context).await, Action::SendMdn => job.send_mdn(context).await, Action::ConfigureImap => job_configure_imap(context).await, - Action::ImexImap => match JobImexImap(context, &job) { + Action::ImexImap => match job_imex_imap(context, &job).await { Ok(()) => Status::Finished(Ok(())), Err(err) => { error!(context, "{}", err); @@ -994,7 +1020,9 @@ async fn perform_job_action( } }, Action::MaybeSendLocations => location::job_maybe_send_locations(context, &job).await, - Action::MaybeSendLocationsEnded => location::JobMaybeSendLocationsEnded(context, &mut job), + Action::MaybeSendLocationsEnded => { + location::job_maybe_send_locations_ended(context, &mut job).await + } Action::Housekeeping => { sql::housekeeping(context); Status::Finished(Ok(())) @@ -1078,9 +1106,7 @@ pub async fn add( let timestamp = time(); let thread: Thread = action.into(); - sql::execute( - context, - &context.sql, + context.sql.execute( "INSERT INTO jobs (added_timestamp, thread, action, foreign_id, param, desired_timestamp) VALUES (?,?,?,?,?,?);", params![ timestamp, @@ -1090,7 +1116,7 @@ pub async fn add( param.to_string(), (timestamp + delay_seconds as i64) ] - ).ok(); + ).await.ok(); match thread { Thread::Imap => interrupt_inbox_idle(context).await, @@ -1114,7 +1140,7 @@ pub async fn interrupt_smtp_idle(context: &Context) { /// IMAP jobs. The `probe_network` parameter decides how to query /// jobs, this is tricky and probably wrong currently. Look at the /// SQL queries for details. -fn load_next_job(context: &Context, thread: Thread, probe_network: bool) -> Option { +async fn load_next_job(context: &Context, thread: Thread, probe_network: bool) -> Option { let query = if !probe_network { // processing for first-try and after backoff-timeouts: // process jobs in the order they were added. @@ -1165,6 +1191,7 @@ fn load_next_job(context: &Context, thread: Thread, probe_network: bool) -> Opti Ok(None) }, ) + .await .unwrap_or_default() } diff --git a/src/job_thread.rs b/src/job_thread.rs index a39c34f44..2d3f843f9 100644 --- a/src/job_thread.rs +++ b/src/job_thread.rs @@ -115,7 +115,7 @@ impl JobThread { let prefix = format!("{}-fetch", self.name); match self.imap.connect_configured(context).await { Ok(()) => { - if let Some(watch_folder) = self.get_watch_folder(context) { + if let Some(watch_folder) = self.get_watch_folder(context).await { let start = std::time::Instant::now(); info!(context, "{} started...", prefix); let res = self @@ -135,8 +135,12 @@ impl JobThread { } } - fn get_watch_folder(&self, context: &Context) -> Option { - match context.sql.get_raw_config(context, self.folder_config_name) { + async fn get_watch_folder(&self, context: &Context) -> Option { + match context + .sql + .get_raw_config(context, self.folder_config_name) + .await + { Some(name) => Some(name), None => { if self.folder_config_name == "configured_inbox_folder" { @@ -184,7 +188,7 @@ impl JobThread { if !self.imap.can_idle().await { true // we have to do fake_idle } else { - let watch_folder = self.get_watch_folder(context); + let watch_folder = self.get_watch_folder(context).await; info!(context, "{} started...", prefix); let res = self.imap.idle(context, watch_folder).await; info!(context, "{} ended...", prefix); @@ -205,7 +209,7 @@ impl JobThread { } }; if do_fake_idle { - let watch_folder = self.get_watch_folder(context); + let watch_folder = self.get_watch_folder(context).await; self.imap.fake_idle(context, watch_folder).await; } diff --git a/src/key.rs b/src/key.rs index 243f09e2c..0429f3a58 100644 --- a/src/key.rs +++ b/src/key.rs @@ -197,7 +197,7 @@ impl Key { } } - pub fn from_self_public( + pub async fn from_self_public( context: &Context, self_addr: impl AsRef, sql: &Sql, @@ -209,10 +209,11 @@ impl Key { "SELECT public_key FROM keypairs WHERE addr=? AND is_default=1;", &[addr], ) + .await .and_then(|blob: Vec| Self::from_slice(&blob, KeyType::Public)) } - pub fn from_self_private( + pub async fn from_self_private( context: &Context, self_addr: impl AsRef, sql: &Sql, @@ -222,6 +223,7 @@ impl Key { "SELECT private_key FROM keypairs WHERE addr=? AND is_default=1;", &[self_addr.as_ref()], ) + .await .and_then(|blob: Vec| Self::from_slice(&blob, KeyType::Private)) } @@ -347,7 +349,7 @@ impl SaveKeyError { /// same key again overwrites it. /// /// [Config::ConfiguredAddr]: crate::config::Config::ConfiguredAddr -pub fn store_self_keypair( +pub async fn store_self_keypair( context: &Context, keypair: &KeyPair, default: KeyPairUse, @@ -368,11 +370,13 @@ pub fn store_self_keypair( "DELETE FROM keypairs WHERE public_key=? OR private_key=?;", params![public_key, secret_key], ) + .await .map_err(|err| SaveKeyError::new("failed to remove old use of key", err))?; if default == KeyPairUse::Default { context .sql .execute("UPDATE keypairs SET is_default=0;", params![]) + .await .map_err(|err| SaveKeyError::new("failed to clear default", err))?; } let is_default = match default { @@ -392,6 +396,7 @@ pub fn store_self_keypair( time() ], ) + .await .map(|_| ()) .map_err(|err| SaveKeyError::new("failed to insert keypair", err)) } diff --git a/src/keyring.rs b/src/keyring.rs index 25d197cd3..4009a0591 100644 --- a/src/keyring.rs +++ b/src/keyring.rs @@ -27,7 +27,7 @@ impl<'a> Keyring<'a> { &self.keys } - pub fn load_self_private_for_decrypting( + pub async fn load_self_private_for_decrypting( &mut self, context: &Context, self_addr: impl AsRef, @@ -38,6 +38,7 @@ impl<'a> Keyring<'a> { "SELECT private_key FROM keypairs ORDER BY addr=? DESC, is_default DESC;", &[self_addr.as_ref()], ) + .await .and_then(|blob: Vec| Key::from_slice(&blob, KeyType::Private)) .map(|key| self.add_owned(key)) .is_some() diff --git a/src/location.rs b/src/location.rs index b494957ae..1ca5cd3f4 100644 --- a/src/location.rs +++ b/src/location.rs @@ -15,7 +15,6 @@ use crate::job::{self, Job}; use crate::message::{Message, MsgId}; use crate::mimeparser::SystemMessage; use crate::param::*; -use crate::sql; use crate::stock::StockMessage; /// Location record @@ -195,21 +194,22 @@ impl Kml { pub async fn send_locations_to_chat(context: &Context, chat_id: ChatId, seconds: i64) { let now = time(); if !(seconds < 0 || chat_id.is_special()) { - let is_sending_locations_before = is_sending_locations_to_chat(context, chat_id); - if sql::execute( - context, - &context.sql, - "UPDATE chats \ + let is_sending_locations_before = is_sending_locations_to_chat(context, chat_id).await; + if context + .sql + .execute( + "UPDATE chats \ SET locations_send_begin=?, \ locations_send_until=? \ WHERE id=?", - params![ - if 0 != seconds { now } else { 0 }, - if 0 != seconds { now + seconds } else { 0 }, - chat_id, - ], - ) - .is_ok() + params![ + if 0 != seconds { now } else { 0 }, + if 0 != seconds { now + seconds } else { 0 }, + chat_id, + ], + ) + .await + .is_ok() { if 0 != seconds && !is_sending_locations_before { let mut msg = Message::new(Viewtype::Text); @@ -241,7 +241,7 @@ pub async fn send_locations_to_chat(context: &Context, chat_id: ChatId, seconds: } async fn schedule_maybe_send_locations(context: &Context, force_schedule: bool) { - if force_schedule || !job::action_exists(context, job::Action::MaybeSendLocations) { + if force_schedule || !job::action_exists(context, job::Action::MaybeSendLocations).await { job::add( context, job::Action::MaybeSendLocations, @@ -253,13 +253,14 @@ async fn schedule_maybe_send_locations(context: &Context, force_schedule: bool) }; } -pub fn is_sending_locations_to_chat(context: &Context, chat_id: ChatId) -> bool { +pub async fn is_sending_locations_to_chat(context: &Context, chat_id: ChatId) -> bool { context .sql .exists( "SELECT id FROM chats WHERE (? OR id=?) AND locations_send_until>?;", params![if chat_id.is_unset() { 1 } else { 0 }, chat_id, time()], ) + .await .unwrap_or_default() } @@ -269,12 +270,16 @@ pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64 } let mut continue_streaming = false; - if let Ok(chats) = context.sql.query_map( - "SELECT id FROM chats WHERE locations_send_until>?;", - params![time()], - |row| row.get::<_, i32>(0), - |chats| chats.collect::, _>>().map_err(Into::into), - ) { + if let Ok(chats) = context + .sql + .query_map( + "SELECT id FROM chats WHERE locations_send_until>?;", + params![time()], + |row| row.get::<_, i32>(0), + |chats| chats.collect::, _>>().map_err(Into::into), + ) + .await + { for chat_id in chats { if let Err(err) = context.sql.execute( "INSERT INTO locations \ @@ -287,7 +292,7 @@ pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64 chat_id, DC_CONTACT_ID_SELF, ] - ) { + ).await { warn!(context, "failed to store location {:?}", err); } else { continue_streaming = true; @@ -302,7 +307,7 @@ pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64 continue_streaming } -pub fn get_range( +pub async fn get_range( context: &Context, chat_id: ChatId, contact_id: u32, @@ -360,6 +365,7 @@ pub fn get_range( Ok(ret) }, ) + .await .unwrap_or_default() } @@ -368,17 +374,21 @@ fn is_marker(txt: &str) -> bool { } /// Deletes all locations from the database. -pub fn delete_all(context: &Context) -> Result<(), Error> { - sql::execute(context, &context.sql, "DELETE FROM locations;", params![])?; +pub async fn delete_all(context: &Context) -> Result<(), Error> { + context + .sql + .execute("DELETE FROM locations;", params![]) + .await?; context.call_cb(Event::LocationChanged(None)); Ok(()) } -pub fn get_kml(context: &Context, chat_id: ChatId) -> Result<(String, u32), Error> { +pub async fn get_kml(context: &Context, chat_id: ChatId) -> Result<(String, u32), Error> { let mut last_added_location_id = 0; let self_addr = context .get_config(Config::ConfiguredAddr) + .await .unwrap_or_default(); let (locations_send_begin, locations_send_until, locations_last_sent) = context.sql.query_row( @@ -389,7 +399,8 @@ pub fn get_kml(context: &Context, chat_id: ChatId) -> Result<(String, u32), Erro let last_sent: i64 = row.get(2)?; Ok((send_begin, send_until, last_sent)) - })?; + }) + .await?; let now = time(); let mut location_count = 0; @@ -433,7 +444,7 @@ pub fn get_kml(context: &Context, chat_id: ChatId) -> Result<(String, u32), Erro } Ok(()) } - )?; + ).await?; ret += "\n"; } @@ -466,37 +477,38 @@ pub fn get_message_kml(timestamp: i64, latitude: f64, longitude: f64) -> String ) } -pub fn set_kml_sent_timestamp( +pub async fn set_kml_sent_timestamp( context: &Context, chat_id: ChatId, timestamp: i64, ) -> Result<(), Error> { - sql::execute( - context, - &context.sql, - "UPDATE chats SET locations_last_sent=? WHERE id=?;", - params![timestamp, chat_id], - )?; - + context + .sql + .execute( + "UPDATE chats SET locations_last_sent=? WHERE id=?;", + params![timestamp, chat_id], + ) + .await?; Ok(()) } -pub fn set_msg_location_id( +pub async fn set_msg_location_id( context: &Context, msg_id: MsgId, location_id: u32, ) -> Result<(), Error> { - sql::execute( - context, - &context.sql, - "UPDATE msgs SET location_id=? WHERE id=?;", - params![location_id, msg_id], - )?; + context + .sql + .execute( + "UPDATE msgs SET location_id=? WHERE id=?;", + params![location_id, msg_id], + ) + .await?; Ok(()) } -pub fn save( +pub async fn save( context: &Context, chat_id: ChatId, contact_id: u32, @@ -504,50 +516,56 @@ pub fn save( independent: bool, ) -> Result { ensure!(!chat_id.is_special(), "Invalid chat id"); - context + + let newest_location_id = context .sql - .prepare2( - "SELECT id FROM locations WHERE timestamp=? AND from_id=?", - "INSERT INTO locations\ + .with_conn(|mut conn| { + let mut newest_timestamp = 0; + let mut newest_location_id = 0; + + for location in locations { + let mut stmt_test = conn + .prepare_cached("SELECT id FROM locations WHERE timestamp=? AND from_id=?")?; + let mut stmt_insert = conn.prepare_cached( + "INSERT INTO locations\ (timestamp, from_id, chat_id, latitude, longitude, accuracy, independent) \ VALUES (?,?,?,?,?,?,?);", - |mut stmt_test, mut stmt_insert, conn| { - let mut newest_timestamp = 0; - let mut newest_location_id = 0; + )?; - for location in locations { - let exists = - stmt_test.exists(params![location.timestamp, contact_id as i32])?; + let exists = stmt_test.exists(params![location.timestamp, contact_id as i32])?; - if independent || !exists { - stmt_insert.execute(params![ + if independent || !exists { + stmt_insert.execute(params![ + location.timestamp, + contact_id as i32, + chat_id, + location.latitude, + location.longitude, + location.accuracy, + independent, + ])?; + + if location.timestamp > newest_timestamp { + // okay to drop, as we use cached prepared statements + drop(stmt_test); + drop(stmt_insert); + newest_timestamp = location.timestamp; + newest_location_id = crate::sql::get_rowid2( + context, + &mut conn, + "locations", + "timestamp", location.timestamp, + "from_id", contact_id as i32, - chat_id, - location.latitude, - location.longitude, - location.accuracy, - independent, - ])?; - - if location.timestamp > newest_timestamp { - newest_timestamp = location.timestamp; - newest_location_id = sql::get_rowid2_with_conn( - context, - conn, - "locations", - "timestamp", - location.timestamp, - "from_id", - contact_id as i32, - ); - } + )?; } } - Ok(newest_location_id) - }, - ) - .map_err(Into::into) + } + Ok(newest_location_id) + }) + .await?; + Ok(newest_location_id) } pub(crate) async fn job_maybe_send_locations(context: &Context, _job: &Job) -> job::Status { @@ -558,80 +576,87 @@ pub(crate) async fn job_maybe_send_locations(context: &Context, _job: &Job) -> j " ----------------- MAYBE_SEND_LOCATIONS -------------- ", ); - if let Ok(rows) = context.sql.query_map( - "SELECT id, locations_send_begin, locations_last_sent \ + if let Ok(ref rows) = context + .sql + .query_map( + "SELECT id, locations_send_begin, locations_last_sent \ FROM chats \ WHERE locations_send_until>?;", - params![now], - |row| { - let chat_id: ChatId = row.get(0)?; - let locations_send_begin: i64 = row.get(1)?; - let locations_last_sent: i64 = row.get(2)?; - continue_streaming = true; + params![now], + |row| { + let chat_id: ChatId = row.get(0)?; + let locations_send_begin: i64 = row.get(1)?; + let locations_last_sent: i64 = row.get(2)?; + continue_streaming = true; - // be a bit tolerant as the timer may not align exactly with time(NULL) - if now - locations_last_sent < (60 - 3) { - Ok(None) - } else { - Ok(Some((chat_id, locations_send_begin, locations_last_sent))) - } - }, - |rows| { - rows.filter_map(|v| v.transpose()) - .collect::, _>>() - .map_err(Into::into) - }, - ) { + // be a bit tolerant as the timer may not align exactly with time(NULL) + if now - locations_last_sent < (60 - 3) { + Ok(None) + } else { + Ok(Some((chat_id, locations_send_begin, locations_last_sent))) + } + }, + |rows| { + rows.filter_map(|v| v.transpose()) + .collect::, _>>() + .map_err(Into::into) + }, + ) + .await + { let msgs = context .sql - .prepare( - "SELECT id \ + .with_conn(|conn| { + let mut stmt_locations = conn.prepare_cached( + "SELECT id \ FROM locations \ WHERE from_id=? \ AND timestamp>=? \ AND timestamp>? \ AND independent=0 \ ORDER BY timestamp;", - |mut stmt_locations, _| { - let msgs = rows - .into_iter() - .filter_map(|(chat_id, locations_send_begin, locations_last_sent)| { - if !stmt_locations - .exists(params![ - DC_CONTACT_ID_SELF, - locations_send_begin, - locations_last_sent, - ]) - .unwrap_or_default() - { - // if there is no new location, there's nothing to send. - // however, maybe we want to bypass this test eg. 15 minutes - None - } else { - // pending locations are attached automatically to every message, - // so also to this empty text message. - // DC_CMD_LOCATION is only needed to create a nicer subject. - // - // for optimisation and to avoid flooding the sending queue, - // we could sending these messages only if we're really online. - // the easiest way to determine this, is to check for an empty message queue. - // (might not be 100%, however, as positions are sent combined later - // and dc_set_location() is typically called periodically, this is ok) - let mut msg = Message::new(Viewtype::Text); - msg.hidden = true; - msg.param.set_cmd(SystemMessage::LocationOnly); - Some((chat_id, msg)) - } - }) - .collect::>(); - Ok(msgs) - }, - ) - .unwrap_or_default(); // TODO: Better error handling + )?; + + let msgs = rows + .iter() + .filter_map(|(chat_id, locations_send_begin, locations_last_sent)| { + if !stmt_locations + .exists(params![ + DC_CONTACT_ID_SELF, + locations_send_begin, + locations_last_sent, + ]) + .unwrap_or_default() + { + // if there is no new location, there's nothing to send. + // however, maybe we want to bypass this test eg. 15 minutes + None + } else { + // pending locations are attached automatically to every message, + // so also to this empty text message. + // DC_CMD_LOCATION is only needed to create a nicer subject. + // + // for optimisation and to avoid flooding the sending queue, + // we could sending these messages only if we're really online. + // the easiest way to determine this, is to check for an empty message queue. + // (might not be 100%, however, as positions are sent combined later + // and dc_set_location() is typically called periodically, this is ok) + let mut msg = Message::new(Viewtype::Text); + msg.hidden = true; + msg.param.set_cmd(SystemMessage::LocationOnly); + Some((chat_id, msg)) + } + }) + .collect::>(); + + Ok(msgs) + }) + .await + .unwrap_or_default(); for (chat_id, mut msg) in msgs.into_iter() { // TODO: better error handling - chat::send_msg(context, chat_id, &mut msg) + chat::send_msg(context, *chat_id, &mut msg) .await .unwrap_or_default(); } @@ -642,19 +667,26 @@ pub(crate) async fn job_maybe_send_locations(context: &Context, _job: &Job) -> j job::Status::Finished(Ok(())) } -#[allow(non_snake_case)] -pub(crate) fn JobMaybeSendLocationsEnded(context: &Context, job: &mut Job) -> job::Status { +pub(crate) async fn job_maybe_send_locations_ended( + context: &Context, + job: &mut Job, +) -> job::Status { // this function is called when location-streaming _might_ have ended for a chat. // the function checks, if location-streaming is really ended; // if so, a device-message is added if not yet done. let chat_id = ChatId::new(job.foreign_id); - let (send_begin, send_until) = job_try!(context.sql.query_row( - "SELECT locations_send_begin, locations_send_until FROM chats WHERE id=?", - params![chat_id], - |row| Ok((row.get::<_, i64>(0)?, row.get::<_, i64>(1)?)), - )); + let (send_begin, send_until) = job_try!( + context + .sql + .query_row( + "SELECT locations_send_begin, locations_send_until FROM chats WHERE id=?", + params![chat_id], + |row| Ok((row.get::<_, i64>(0)?, row.get::<_, i64>(1)?)), + ) + .await + ); if !(send_begin != 0 && time() <= send_until) { // still streaming - @@ -665,7 +697,7 @@ pub(crate) fn JobMaybeSendLocationsEnded(context: &Context, job: &mut Job) -> jo job_try!(context.sql.execute( "UPDATE chats SET locations_send_begin=0, locations_send_until=0 WHERE id=?", params![chat_id], - )); + ).await); let stock_str = context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0); chat::add_info_msg(context, chat_id, stock_str); diff --git a/src/login_param.rs b/src/login_param.rs index be55feacd..7309a90ad 100644 --- a/src/login_param.rs +++ b/src/login_param.rs @@ -50,59 +50,69 @@ impl LoginParam { } /// Read the login parameters from the database. - pub fn from_database(context: &Context, prefix: impl AsRef) -> Self { + pub async fn from_database(context: &Context, prefix: impl AsRef) -> Self { let prefix = prefix.as_ref(); let sql = &context.sql; let key = format!("{}addr", prefix); let addr = sql .get_raw_config(context, key) + .await .unwrap_or_default() .trim() .to_string(); let key = format!("{}mail_server", prefix); - let mail_server = sql.get_raw_config(context, key).unwrap_or_default(); + let mail_server = sql.get_raw_config(context, key).await.unwrap_or_default(); let key = format!("{}mail_port", prefix); - let mail_port = sql.get_raw_config_int(context, key).unwrap_or_default(); + let mail_port = sql + .get_raw_config_int(context, key) + .await + .unwrap_or_default(); let key = format!("{}mail_user", prefix); - let mail_user = sql.get_raw_config(context, key).unwrap_or_default(); + let mail_user = sql.get_raw_config(context, key).await.unwrap_or_default(); let key = format!("{}mail_pw", prefix); - let mail_pw = sql.get_raw_config(context, key).unwrap_or_default(); + let mail_pw = sql.get_raw_config(context, key).await.unwrap_or_default(); let key = format!("{}imap_certificate_checks", prefix); let imap_certificate_checks = - if let Some(certificate_checks) = sql.get_raw_config_int(context, key) { + if let Some(certificate_checks) = sql.get_raw_config_int(context, key).await { num_traits::FromPrimitive::from_i32(certificate_checks).unwrap() } else { Default::default() }; let key = format!("{}send_server", prefix); - let send_server = sql.get_raw_config(context, key).unwrap_or_default(); + let send_server = sql.get_raw_config(context, key).await.unwrap_or_default(); let key = format!("{}send_port", prefix); - let send_port = sql.get_raw_config_int(context, key).unwrap_or_default(); + let send_port = sql + .get_raw_config_int(context, key) + .await + .unwrap_or_default(); let key = format!("{}send_user", prefix); - let send_user = sql.get_raw_config(context, key).unwrap_or_default(); + let send_user = sql.get_raw_config(context, key).await.unwrap_or_default(); let key = format!("{}send_pw", prefix); - let send_pw = sql.get_raw_config(context, key).unwrap_or_default(); + let send_pw = sql.get_raw_config(context, key).await.unwrap_or_default(); let key = format!("{}smtp_certificate_checks", prefix); let smtp_certificate_checks = - if let Some(certificate_checks) = sql.get_raw_config_int(context, key) { + if let Some(certificate_checks) = sql.get_raw_config_int(context, key).await { num_traits::FromPrimitive::from_i32(certificate_checks).unwrap() } else { Default::default() }; let key = format!("{}server_flags", prefix); - let server_flags = sql.get_raw_config_int(context, key).unwrap_or_default(); + let server_flags = sql + .get_raw_config_int(context, key) + .await + .unwrap_or_default(); LoginParam { addr, @@ -125,7 +135,7 @@ impl LoginParam { } /// Save this loginparam to the database. - pub fn save_to_database( + pub async fn save_to_database( &self, context: &Context, prefix: impl AsRef, @@ -134,40 +144,49 @@ impl LoginParam { let sql = &context.sql; let key = format!("{}addr", prefix); - sql.set_raw_config(context, key, Some(&self.addr))?; + sql.set_raw_config(context, key, Some(&self.addr)).await?; let key = format!("{}mail_server", prefix); - sql.set_raw_config(context, key, Some(&self.mail_server))?; + sql.set_raw_config(context, key, Some(&self.mail_server)) + .await?; let key = format!("{}mail_port", prefix); - sql.set_raw_config_int(context, key, self.mail_port)?; + sql.set_raw_config_int(context, key, self.mail_port).await?; let key = format!("{}mail_user", prefix); - sql.set_raw_config(context, key, Some(&self.mail_user))?; + sql.set_raw_config(context, key, Some(&self.mail_user)) + .await?; let key = format!("{}mail_pw", prefix); - sql.set_raw_config(context, key, Some(&self.mail_pw))?; + sql.set_raw_config(context, key, Some(&self.mail_pw)) + .await?; let key = format!("{}imap_certificate_checks", prefix); - sql.set_raw_config_int(context, key, self.imap_certificate_checks as i32)?; + sql.set_raw_config_int(context, key, self.imap_certificate_checks as i32) + .await?; let key = format!("{}send_server", prefix); - sql.set_raw_config(context, key, Some(&self.send_server))?; + sql.set_raw_config(context, key, Some(&self.send_server)) + .await?; let key = format!("{}send_port", prefix); - sql.set_raw_config_int(context, key, self.send_port)?; + sql.set_raw_config_int(context, key, self.send_port).await?; let key = format!("{}send_user", prefix); - sql.set_raw_config(context, key, Some(&self.send_user))?; + sql.set_raw_config(context, key, Some(&self.send_user)) + .await?; let key = format!("{}send_pw", prefix); - sql.set_raw_config(context, key, Some(&self.send_pw))?; + sql.set_raw_config(context, key, Some(&self.send_pw)) + .await?; let key = format!("{}smtp_certificate_checks", prefix); - sql.set_raw_config_int(context, key, self.smtp_certificate_checks as i32)?; + sql.set_raw_config_int(context, key, self.smtp_certificate_checks as i32) + .await?; let key = format!("{}server_flags", prefix); - sql.set_raw_config_int(context, key, self.server_flags)?; + sql.set_raw_config_int(context, key, self.server_flags) + .await?; Ok(()) } diff --git a/src/message.rs b/src/message.rs index b2a7c7e21..401f93be7 100644 --- a/src/message.rs +++ b/src/message.rs @@ -19,7 +19,6 @@ use crate::lot::{Lot, LotState, Meaning}; use crate::mimeparser::SystemMessage; use crate::param::*; use crate::pgp::*; -use crate::sql; use crate::stock::StockMessage; lazy_static! { @@ -219,12 +218,12 @@ impl Message { msg } - pub fn load_from_db(context: &Context, id: MsgId) -> Result { + pub async fn load_from_db(context: &Context, id: MsgId) -> Result { ensure!( !id.is_special(), "Can not load special message IDs from DB." ); - context + let msg = context .sql .query_row( concat!( @@ -302,25 +301,23 @@ impl Message { Ok(msg) }, ) - .map_err(Into::into) + .await?; + + Ok(msg) } - pub fn delete_from_db(context: &Context, msg_id: MsgId) { - if let Ok(msg) = Message::load_from_db(context, msg_id) { - sql::execute( - context, - &context.sql, - "DELETE FROM msgs WHERE id=?;", - params![msg.id], - ) - .ok(); - sql::execute( - context, - &context.sql, - "DELETE FROM msgs_mdns WHERE msg_id=?;", - params![msg.id], - ) - .ok(); + pub async fn delete_from_db(context: &Context, msg_id: MsgId) { + if let Ok(msg) = Message::load_from_db(context, msg_id).await { + context + .sql + .execute("DELETE FROM msgs WHERE id=?;", params![msg.id]) + .await + .ok(); + context + .sql + .execute("DELETE FROM msgs_mdns WHERE msg_id=?;", params![msg.id]) + .await + .ok(); } } @@ -477,13 +474,13 @@ impl Message { self.param.get_int(Param::GuaranteeE2ee).unwrap_or_default() != 0 } - pub fn get_summary(&mut self, context: &Context, chat: Option<&Chat>) -> Lot { + pub async fn get_summary(&mut self, context: &Context, chat: Option<&Chat>) -> Lot { let mut ret = Lot::new(); let chat_loaded: Chat; let chat = if let Some(chat) = chat { chat - } else if let Ok(chat) = Chat::load_from_db(context, self.chat_id) { + } else if let Ok(chat) = Chat::load_from_db(context, self.chat_id).await { chat_loaded = chat; &chat_loaded } else { @@ -493,7 +490,7 @@ impl Message { let contact = if self.from_id != DC_CONTACT_ID_SELF as u32 && (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup) { - Contact::get_by_id(context, self.from_id).ok() + Contact::get_by_id(context, self.from_id).await.ok() } else { None }; @@ -615,14 +612,15 @@ impl Message { self.save_param_to_disk(context); } - pub fn save_param_to_disk(&mut self, context: &Context) -> bool { - sql::execute( - context, - &context.sql, - "UPDATE msgs SET param=? WHERE id=?;", - params![self.param.to_string(), self.id], - ) - .is_ok() + pub async fn save_param_to_disk(&mut self, context: &Context) -> bool { + context + .sql + .execute( + "UPDATE msgs SET param=? WHERE id=?;", + params![self.param.to_string(), self.id], + ) + .await + .is_ok() } } @@ -793,21 +791,24 @@ impl Lot { } } -pub fn get_msg_info(context: &Context, msg_id: MsgId) -> String { +pub async fn get_msg_info(context: &Context, msg_id: MsgId) -> String { let mut ret = String::new(); - let msg = Message::load_from_db(context, msg_id); + let msg = Message::load_from_db(context, msg_id).await; if msg.is_err() { return ret; } let msg = msg.unwrap_or_default(); - let rawtxt: Option = context.sql.query_get_value( - context, - "SELECT txt_raw FROM msgs WHERE id=?;", - params![msg_id], - ); + let rawtxt: Option = context + .sql + .query_get_value( + context, + "SELECT txt_raw FROM msgs WHERE id=?;", + params![msg_id], + ) + .await; if rawtxt.is_none() { ret += &format!("Cannot load message {}.", msg_id); @@ -820,6 +821,7 @@ pub fn get_msg_info(context: &Context, msg_id: MsgId) -> String { ret += &format!("Sent: {}", fts); let name = Contact::load_from_db(context, msg.from_id) + .await .map(|contact| contact.get_name_n_addr()) .unwrap_or_default(); @@ -952,7 +954,7 @@ pub fn get_mime_headers(context: &Context, msg_id: MsgId) -> Option { pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) { for msg_id in msg_ids.iter() { - if let Ok(msg) = Message::load_from_db(context, *msg_id) { + if let Ok(msg) = Message::load_from_db(context, *msg_id).await { if msg.location_id > 0 { delete_poi_location(context, msg.location_id); } @@ -978,24 +980,26 @@ pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) { } } -fn update_msg_chat_id(context: &Context, msg_id: MsgId, chat_id: ChatId) -> bool { - sql::execute( - context, - &context.sql, - "UPDATE msgs SET chat_id=? WHERE id=?;", - params![chat_id, msg_id], - ) - .is_ok() +async fn update_msg_chat_id(context: &Context, msg_id: MsgId, chat_id: ChatId) -> bool { + context + .sql + .execute( + "UPDATE msgs SET chat_id=? WHERE id=?;", + params![chat_id, msg_id], + ) + .await + .is_ok() } -fn delete_poi_location(context: &Context, location_id: u32) -> bool { - sql::execute( - context, - &context.sql, - "DELETE FROM locations WHERE independent = 1 AND id=?;", - params![location_id as i32], - ) - .is_ok() +async fn delete_poi_location(context: &Context, location_id: u32) -> bool { + context + .sql + .execute( + "DELETE FROM locations WHERE independent = 1 AND id=?;", + params![location_id as i32], + ) + .await + .is_ok() } pub async fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool { @@ -1003,16 +1007,18 @@ pub async fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool { return false; } - let msgs = context.sql.prepare( - concat!( - "SELECT", - " m.state AS state,", - " c.blocked AS blocked", - " FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id", - " WHERE m.id=? AND m.chat_id>9" - ), - |mut stmt, _| { - let mut res = Vec::with_capacity(msg_ids.len()); + let msgs = context + .sql + .with_conn(|conn| { + let mut stmt = conn.prepare_cached(concat!( + "SELECT", + " m.state AS state,", + " c.blocked AS blocked", + " FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id", + " WHERE m.id=? AND m.chat_id>9" + ))?; + + let mut msgs = Vec::with_capacity(msg_ids.len()); for id in msg_ids.iter() { let query_res = stmt.query_row(params![*id], |row| { Ok(( @@ -1024,20 +1030,18 @@ pub async fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool { if let Err(rusqlite::Error::QueryReturnedNoRows) = query_res { continue; } - let (state, blocked) = query_res?; - res.push((id, state, blocked)); + let (state, blocked) = query_res + .map_err(|err| Error::SqlError(err.into())) + .expect("query fail"); + msgs.push((id, state, blocked)); } - Ok(res) - }, - ); + Ok(msgs) + }) + .await + .unwrap_or_default(); - if msgs.is_err() { - warn!(context, "markseen_msgs failed: {:?}", msgs); - return false; - } let mut send_event = false; - let msgs = msgs.unwrap_or_default(); for (id, curr_state, curr_blocked) in msgs.into_iter() { if curr_blocked == Blocked::Not { @@ -1071,14 +1075,15 @@ pub async fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool { true } -pub fn update_msg_state(context: &Context, msg_id: MsgId, state: MessageState) -> bool { - sql::execute( - context, - &context.sql, - "UPDATE msgs SET state=? WHERE id=?;", - params![state, msg_id], - ) - .is_ok() +pub async fn update_msg_state(context: &Context, msg_id: MsgId, state: MessageState) -> bool { + context + .sql + .execute( + "UPDATE msgs SET state=? WHERE id=?;", + params![state, msg_id], + ) + .await + .is_ok() } pub fn star_msgs(context: &Context, msg_ids: &[MsgId], star: bool) -> bool { @@ -1189,8 +1194,8 @@ pub fn exists(context: &Context, msg_id: MsgId) -> bool { } } -pub fn set_msg_failed(context: &Context, msg_id: MsgId, error: Option>) { - if let Ok(mut msg) = Message::load_from_db(context, msg_id) { +pub async fn set_msg_failed(context: &Context, msg_id: MsgId, error: Option>) { + if let Ok(mut msg) = Message::load_from_db(context, msg_id).await { if msg.state.can_fail() { msg.state = MessageState::OutFailed; } @@ -1199,13 +1204,14 @@ pub fn set_msg_failed(context: &Context, msg_id: MsgId, error: Option MimeFactory<'a, 'b> { }) } - fn peerstates_for_recipients(&self) -> Result, &str)>, Error> { + async fn peerstates_for_recipients(&self) -> Result>, &str)>, Error> { let self_addr = self .context .get_config(Config::ConfiguredAddr) + .await .ok_or_else(|| format_err!("Not configured"))?; Ok(self diff --git a/src/mimeparser.rs b/src/mimeparser.rs index c95863f69..51f20bdef 100644 --- a/src/mimeparser.rs +++ b/src/mimeparser.rs @@ -80,7 +80,7 @@ impl Default for SystemMessage { const MIME_AC_SETUP_FILE: &str = "application/autocrypt-setup"; impl MimeMessage { - pub fn from_bytes(context: &Context, body: &[u8]) -> Result { + pub async fn from_bytes(context: &Context, body: &[u8]) -> Result { let mail = mailparse::parse_mail(body)?; let message_time = mail @@ -98,7 +98,7 @@ impl MimeMessage { let mail_raw; let mut gossipped_addr = Default::default(); - let (mail, signatures) = match e2ee::try_decrypt(context, &mail, message_time) { + let (mail, signatures) = match e2ee::try_decrypt(context, &mail, message_time).await { Ok((raw, signatures)) => { if let Some(raw) = raw { // Valid autocrypt message, encrypted @@ -114,7 +114,8 @@ impl MimeMessage { let gossip_headers = decrypted_mail.headers.get_all_values("Autocrypt-Gossip")?; gossipped_addr = - update_gossip_peerstates(context, message_time, &mail, gossip_headers)?; + update_gossip_peerstates(context, message_time, &mail, gossip_headers) + .await?; // let known protected headers from the decrypted // part override the unencrypted top-level @@ -837,7 +838,7 @@ impl MimeMessage { let mut param = Params::new(); param.set(Param::ServerFolder, server_folder.as_ref()); param.set_int(Param::ServerUid, server_uid as i32); - if self.has_chat_version() && context.get_config_bool(Config::MvboxMove) { + if self.has_chat_version() && context.get_config_bool(Config::MvboxMove).await { param.set_int(Param::AlsoMove, 1); } job::add(context, Action::MarkseenMdnOnImap, 0, param, 0).await; @@ -845,7 +846,7 @@ impl MimeMessage { } } -fn update_gossip_peerstates( +async fn update_gossip_peerstates( context: &Context, message_time: i64, mail: &mailparse::ParsedMail<'_>, @@ -878,15 +879,15 @@ fn update_gossip_peerstates( let mut peerstate = Peerstate::from_addr(context, &context.sql, &header.addr); if let Some(ref mut peerstate) = peerstate { peerstate.apply_gossip(header, message_time); - peerstate.save_to_db(&context.sql, false)?; + peerstate.save_to_db(&context.sql, false).await?; } else { let p = Peerstate::from_gossip(context, header, message_time); - p.save_to_db(&context.sql, true)?; + p.save_to_db(&context.sql, true).await?; peerstate = Some(p); } if let Some(peerstate) = peerstate { if peerstate.degrade_event.is_some() { - handle_degrade_event(context, &peerstate)?; + handle_degrade_event(context, &peerstate).await?; } } diff --git a/src/peerstate.rs b/src/peerstate.rs index e2a17e0ce..52a591dcb 100644 --- a/src/peerstate.rs +++ b/src/peerstate.rs @@ -9,7 +9,7 @@ use crate::aheader::*; use crate::constants::*; use crate::context::Context; use crate::key::{Key, SignedPublicKey}; -use crate::sql::{self, Sql}; +use crate::sql::Sql; #[derive(Debug)] pub enum PeerstateKeyType { @@ -409,20 +409,17 @@ impl<'a> Peerstate<'a> { } } - pub fn save_to_db(&self, sql: &Sql, create: bool) -> crate::sql::Result<()> { + pub async fn save_to_db(&self, sql: &Sql, create: bool) -> crate::sql::Result<()> { if create { - sql::execute( - self.context, - sql, + sql.execute( "INSERT INTO acpeerstates (addr) VALUES(?);", params![self.addr], - )?; + ) + .await?; } if self.to_save == Some(ToSave::All) || create { - sql::execute( - self.context, - sql, + sql.execute( "UPDATE acpeerstates \ SET last_seen=?, last_seen_autocrypt=?, prefer_encrypted=?, \ public_key=?, gossip_timestamp=?, gossip_key=?, public_key_fingerprint=?, gossip_key_fingerprint=?, \ @@ -441,11 +438,9 @@ impl<'a> Peerstate<'a> { &self.verified_key_fingerprint, &self.addr, ], - )?; + ).await?; } else if self.to_save == Some(ToSave::Timestamps) { - sql::execute( - self.context, - sql, + sql.execute( "UPDATE acpeerstates SET last_seen=?, last_seen_autocrypt=?, gossip_timestamp=? \ WHERE addr=?;", params![ @@ -454,7 +449,8 @@ impl<'a> Peerstate<'a> { self.gossip_timestamp, &self.addr ], - )?; + ) + .await?; } Ok(()) diff --git a/src/qr.rs b/src/qr.rs index 1dc35b50f..8c69655df 100644 --- a/src/qr.rs +++ b/src/qr.rs @@ -40,13 +40,13 @@ impl Into for Error { /// Check a scanned QR code. /// The function should be called after a QR code is scanned. /// The function takes the raw text scanned and checks what can be done with it. -pub fn check_qr(context: &Context, qr: impl AsRef) -> Lot { +pub async fn check_qr(context: &Context, qr: impl AsRef) -> Lot { let qr = qr.as_ref(); info!(context, "Scanned QR code: {}", qr); if qr.starts_with(OPENPGP4FPR_SCHEME) { - decode_openpgp(context, qr) + decode_openpgp(context, qr).await } else if qr.starts_with(DCACCOUNT_SCHEME) { decode_account(context, qr) } else if qr.starts_with(MAILTO_SCHEME) { @@ -66,7 +66,7 @@ pub fn check_qr(context: &Context, qr: impl AsRef) -> Lot { /// scheme: `OPENPGP4FPR:FINGERPRINT#a=ADDR&n=NAME&i=INVITENUMBER&s=AUTH` /// or: `OPENPGP4FPR:FINGERPRINT#a=ADDR&g=GROUPNAME&x=GROUPID&i=INVITENUMBER&s=AUTH` -fn decode_openpgp(context: &Context, qr: &str) -> Lot { +async fn decode_openpgp(context: &Context, qr: &str) -> Lot { let payload = &qr[OPENPGP4FPR_SCHEME.len()..]; let (fingerprint, fragment) = match payload.find('#').map(|offset| { @@ -152,10 +152,12 @@ fn decode_openpgp(context: &Context, qr: &str) -> Lot { peerstate.addr.clone(), Origin::UnhandledQrScan, ) + .await .map(|(id, _)| id) .unwrap_or_default(); let (id, _) = chat::create_or_lookup_by_contact_id(context, lot.id, Blocked::Deaddrop) + .await .unwrap_or_default(); chat::add_info_msg(context, id, format!("{} verified.", peerstate.addr)); @@ -172,6 +174,7 @@ fn decode_openpgp(context: &Context, qr: &str) -> Lot { lot.state = LotState::QrAskVerifyContact; } lot.id = Contact::add_or_lookup(context, &name, &addr, Origin::UnhandledQrScan) + .await .map(|(id, _)| id) .unwrap_or_default(); diff --git a/src/securejoin.rs b/src/securejoin.rs index 44f045842..feda5657f 100644 --- a/src/securejoin.rs +++ b/src/securejoin.rs @@ -143,35 +143,40 @@ fn get_self_fingerprint(context: &Context) -> Option { None } +async fn cleanup( + context: &Context, + contact_chat_id: ChatId, + ongoing_allocated: bool, + join_vg: bool, +) -> ChatId { + let mut bob = context.bob.write().unwrap(); + bob.expects = 0; + let ret_chat_id: ChatId = if bob.status == DC_BOB_SUCCESS { + if join_vg { + chat::get_chat_id_by_grpid( + context, + bob.qr_scan.as_ref().unwrap().text2.as_ref().unwrap(), + ) + .await + .unwrap_or((ChatId::new(0), false, Blocked::Not)) + .0 + } else { + contact_chat_id + } + } else { + ChatId::new(0) + }; + bob.qr_scan = None; + + if ongoing_allocated { + context.free_ongoing(); + } + ret_chat_id +} + /// Take a scanned QR-code and do the setup-contact/join-group handshake. /// See the ffi-documentation for more details. pub async fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId { - let cleanup = - |context: &Context, contact_chat_id: ChatId, ongoing_allocated: bool, join_vg: bool| { - let mut bob = context.bob.write().unwrap(); - bob.expects = 0; - let ret_chat_id: ChatId = if bob.status == DC_BOB_SUCCESS { - if join_vg { - chat::get_chat_id_by_grpid( - context, - bob.qr_scan.as_ref().unwrap().text2.as_ref().unwrap(), - ) - .unwrap_or((ChatId::new(0), false, Blocked::Not)) - .0 - } else { - contact_chat_id - } - } else { - ChatId::new(0) - }; - bob.qr_scan = None; - - if ongoing_allocated { - context.free_ongoing(); - } - ret_chat_id - }; - /*======================================================== ==== Bob - the joiner's side ===== ==== Step 2 in "Setup verified contact" protocol ===== @@ -181,25 +186,25 @@ pub async fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId { let mut join_vg: bool = false; info!(context, "Requesting secure-join ...",); - ensure_secret_key_exists(context).ok(); + ensure_secret_key_exists(context).await.ok(); if !context.alloc_ongoing() { - return cleanup(&context, contact_chat_id, false, join_vg); + return cleanup(&context, contact_chat_id, false, join_vg).await; } - let qr_scan = check_qr(context, &qr); + let qr_scan = check_qr(context, &qr).await; if qr_scan.state != LotState::QrAskVerifyContact && qr_scan.state != LotState::QrAskVerifyGroup { error!(context, "Unknown QR code.",); - return cleanup(&context, contact_chat_id, true, join_vg); + return cleanup(&context, contact_chat_id, true, join_vg).await; } - contact_chat_id = match chat::create_by_contact_id(context, qr_scan.id) { + contact_chat_id = match chat::create_by_contact_id(context, qr_scan.id).await { Ok(chat_id) => chat_id, Err(_) => { error!(context, "Unknown contact."); - return cleanup(&context, contact_chat_id, true, join_vg); + return cleanup(&context, contact_chat_id, true, join_vg).await; } }; if context.shall_stop_ongoing() { - return cleanup(&context, contact_chat_id, true, join_vg); + return cleanup(&context, contact_chat_id, true, join_vg).await; } join_vg = qr_scan.get_state() == LotState::QrAskVerifyGroup; { @@ -266,7 +271,7 @@ pub async fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId { while !context.shall_stop_ongoing() { std::thread::sleep(std::time::Duration::from_millis(200)); } - cleanup(&context, contact_chat_id, true, join_vg) + cleanup(&context, contact_chat_id, true, join_vg).await } else { // for a one-to-one-chat, the chat is already known, return the chat-id, // the verification runs in background @@ -412,7 +417,7 @@ pub(crate) async fn handle_securejoin_handshake( ); let contact_chat_id = - match chat::create_or_lookup_by_contact_id(context, contact_id, Blocked::Not) { + match chat::create_or_lookup_by_contact_id(context, contact_id, Blocked::Not).await { Ok((chat_id, blocked)) => { if blocked != Blocked::Not { chat_id.unblock(context); @@ -447,7 +452,7 @@ pub(crate) async fn handle_securejoin_handshake( return Ok(HandshakeMessage::Ignore); } }; - if !token::exists(context, token::Namespace::InviteNumber, &invitenumber) { + if !token::exists(context, token::Namespace::InviteNumber, &invitenumber).await { warn!(context, "Secure-join denied (bad invitenumber)."); return Ok(HandshakeMessage::Ignore); } @@ -583,7 +588,7 @@ pub(crate) async fn handle_securejoin_handshake( return Ok(HandshakeMessage::Ignore); } }; - if !token::exists(context, token::Namespace::Auth, &auth_0) { + if !token::exists(context, token::Namespace::Auth, &auth_0).await { could_not_establish_secure_connection(context, contact_chat_id, "Auth invalid."); return Ok(HandshakeMessage::Ignore); } @@ -611,7 +616,7 @@ pub(crate) async fn handle_securejoin_handshake( return Ok(HandshakeMessage::Ignore); } }; - match chat::get_chat_id_by_grpid(context, field_grpid) { + match chat::get_chat_id_by_grpid(context, field_grpid).await { Ok((group_chat_id, _, _)) => { if let Err(err) = chat::add_contact_to_chat_ex(context, group_chat_id, contact_id, true) @@ -672,6 +677,7 @@ pub(crate) async fn handle_securejoin_handshake( // only after we have returned. It does not impact // the security invariants of secure-join however. let (_, is_verified_group, _) = chat::get_chat_id_by_grpid(context, &group_id) + .await .unwrap_or((ChatId::new(0), false, Blocked::Not)); // when joining a non-verified group // the vg-member-added message may be unencrypted @@ -711,6 +717,7 @@ pub(crate) async fn handle_securejoin_handshake( if join_vg && !context .is_self_addr(cg_member_added) + .await .map_err(|_| HandshakeError::NoSelfAddr)? { info!(context, "Message belongs to a different handshake (scaled up contact anyway to allow creation of group)."); @@ -744,7 +751,7 @@ pub(crate) async fn handle_securejoin_handshake( ==== Step 8 in "Out-of-band verified groups" protocol ==== ==========================================================*/ - if let Ok(contact) = Contact::get_by_id(context, contact_id) { + if let Ok(contact) = Contact::get_by_id(context, contact_id).await { if contact.is_verified(context) == VerifiedStatus::Unverified { warn!(context, "vg-member-added-received invalid.",); return Ok(HandshakeMessage::Ignore); @@ -756,6 +763,7 @@ pub(crate) async fn handle_securejoin_handshake( .map(|s| s.as_str()) .unwrap_or_else(|| ""); let (group_chat_id, _, _) = chat::get_chat_id_by_grpid(context, &field_grpid) + .await .map_err(|err| { warn!(context, "Failed to lookup chat_id from grpid: {}", err); HandshakeError::ChatNotFound { @@ -868,18 +876,25 @@ fn encrypted_and_signed( } } -pub fn handle_degrade_event(context: &Context, peerstate: &Peerstate) -> Result<(), Error> { +pub async fn handle_degrade_event( + context: &Context, + peerstate: &Peerstate<'_>, +) -> Result<(), Error> { // - we do not issue an warning for DC_DE_ENCRYPTION_PAUSED as this is quite normal // - currently, we do not issue an extra warning for DC_DE_VERIFICATION_LOST - this always comes // together with DC_DE_FINGERPRINT_CHANGED which is logged, the idea is not to bother // with things they cannot fix, so the user is just kicked from the verified group // (and he will know this and can fix this) if Some(DegradeEvent::FingerprintChanged) == peerstate.degrade_event { - let contact_id: i32 = match context.sql.query_get_value( - context, - "SELECT id FROM contacts WHERE addr=?;", - params![&peerstate.addr], - ) { + let contact_id: i32 = match context + .sql + .query_get_value( + context, + "SELECT id FROM contacts WHERE addr=?;", + params![&peerstate.addr], + ) + .await + { None => bail!( "contact with peerstate.addr {:?} not found", &peerstate.addr @@ -889,6 +904,7 @@ pub fn handle_degrade_event(context: &Context, peerstate: &Peerstate) -> Result< if contact_id > 0 { let (contact_chat_id, _) = chat::create_or_lookup_by_contact_id(context, contact_id as u32, Blocked::Deaddrop) + .await .unwrap_or_default(); let msg = context diff --git a/src/sql.rs b/src/sql.rs index 8e15d1fdd..f5a08f1d3 100644 --- a/src/sql.rs +++ b/src/sql.rs @@ -1,10 +1,13 @@ //! # SQLite wrapper +use async_std::prelude::*; +use async_std::sync::{Arc, RwLock}; + use std::collections::HashSet; -use std::sync::{Arc, RwLock}; +use std::path::Path; use std::time::Duration; -use rusqlite::{Connection, OpenFlags, Statement, NO_PARAMS}; +use rusqlite::{Connection, Error as SqlError, OpenFlags, NO_PARAMS}; use thread_local_object::ThreadLocal; use crate::chat::{update_device_icon, update_saved_messages_icon}; @@ -80,12 +83,12 @@ impl Sql { Self::default() } - pub fn is_open(&self) -> bool { - self.pool.read().unwrap().is_some() + pub async fn is_open(&self) -> bool { + self.pool.read().await.is_some() } - pub fn close(&self, context: &Context) { - let _ = self.pool.write().unwrap().take(); + pub async fn close(&self, context: &Context) { + let _ = self.pool.write().await.take(); self.in_use.remove(); // drop closes the connection @@ -93,79 +96,36 @@ impl Sql { } // return true on success, false on failure - pub fn open(&self, context: &Context, dbfile: &std::path::Path, readonly: bool) -> bool { - match open(context, self, dbfile, readonly) { + pub async fn open(&self, context: &Context, dbfile: &Path, readonly: bool) -> bool { + match open(context, self, dbfile, readonly).await { Ok(_) => true, Err(crate::error::Error::SqlError(Error::SqlAlreadyOpen)) => false, Err(_) => { - self.close(context); + self.close(context).await; false } } } - pub fn execute

(&self, sql: &str, params: P) -> Result + pub async fn execute>(&self, sql: S, params: P) -> Result where P: IntoIterator, P::Item: rusqlite::ToSql, { - self.start_stmt(sql.to_string()); - self.with_conn(|conn| conn.execute(sql, params).map_err(Into::into)) - } + self.start_stmt(sql.as_ref()); - pub fn with_conn(&self, g: G) -> Result - where - G: FnOnce(&mut Connection) -> Result, - { - let res = match &*self.pool.read().unwrap() { - Some(pool) => { - let mut conn = pool.get()?; - - // Only one process can make changes to the database at one time. - // busy_timeout defines, that if a second process wants write access, - // this second process will wait some milliseconds - // and try over until it gets write access or the given timeout is elapsed. - // If the second process does not get write access within the given timeout, - // sqlite3_step() will return the error SQLITE_BUSY. - // (without a busy_timeout, sqlite3_step() would return SQLITE_BUSY _at once_) - conn.busy_timeout(Duration::from_secs(10))?; - - g(&mut conn) - } - None => Err(Error::SqlNoConnection), + let res = { + let conn = self.get_conn().await?; + conn.execute(sql.as_ref(), params)? }; - self.in_use.remove(); - res - } - pub fn prepare(&self, sql: &str, g: G) -> Result - where - G: FnOnce(Statement<'_>, &Connection) -> Result, - { - self.start_stmt(sql.to_string()); - self.with_conn(|conn| { - let stmt = conn.prepare(sql)?; - g(stmt, conn) - }) - } - - pub fn prepare2(&self, sql1: &str, sql2: &str, g: G) -> Result - where - G: FnOnce(Statement<'_>, Statement<'_>, &Connection) -> Result, - { - self.start_stmt(format!("{} - {}", sql1, sql2)); - self.with_conn(|conn| { - let stmt1 = conn.prepare(sql1)?; - let stmt2 = conn.prepare(sql2)?; - - g(stmt1, stmt2, conn) - }) + Ok(res) } /// Prepares and executes the statement and maps a function over the resulting rows. /// Then executes the second function over the returned iterator and returns the /// result of that function. - pub fn query_map( + pub async fn query_map( &self, sql: impl AsRef, params: P, @@ -179,54 +139,153 @@ impl Sql { G: FnMut(rusqlite::MappedRows) -> Result, { self.start_stmt(sql.as_ref().to_string()); - self.with_conn(|conn| { - let mut stmt = conn.prepare(sql.as_ref())?; + let sql = sql.as_ref(); + + let res = { + let conn = self.get_conn().await?; + let mut stmt = conn.prepare(sql)?; let res = stmt.query_map(params, f)?; - g(res) - }) + g(res)? + }; + + Ok(res) + } + + /// Prepares and executes the statement and maps a function over the resulting rows. + /// Then executes the second function over the returned iterator and returns the + /// result of that function. + pub async fn query_map_async<'a, T, P, F, G, H, Fut>( + &self, + sql: impl AsRef, + params: P, + f: F, + g: G, + ) -> Result + where + P: IntoIterator, + P::Item: rusqlite::ToSql, + F: FnMut(&rusqlite::Row) -> rusqlite::Result, + G: FnMut(rusqlite::MappedRows<'a, F>) -> Fut, + Fut: Future> + 'a, + { + self.start_stmt(sql.as_ref().to_string()); + unimplemented!() + // let sql = sql.as_ref(); + // self.with_conn_async(|conn| async move { + // let mut stmt = conn.prepare(sql)?; + // { + // let res = stmt.query_map(params, f)?; + // g(res).await + // } + // }) + // .await + } + + pub async fn get_conn( + &self, + ) -> Result> { + let lock = self.pool.read().await; + let pool = lock.as_ref().ok_or_else(|| Error::SqlNoConnection)?; + let conn = pool.get()?; + + Ok(conn) + } + + pub async fn with_conn(&self, mut g: G) -> Result + where + G: FnMut(r2d2::PooledConnection) -> Result, + { + let lock = self.pool.read().await; + let pool = lock.as_ref().ok_or_else(|| Error::SqlNoConnection)?; + + let res = { + let conn = pool.get()?; + let res = g(conn); + self.in_use.remove(); + res + }; + res + } + + pub async fn with_conn_async(&self, mut g: G) -> Result + where + G: FnMut(r2d2::PooledConnection) -> Fut, + Fut: Future> + Send, + { + let lock = self.pool.read().await; + let pool = lock.as_ref().ok_or_else(|| Error::SqlNoConnection)?; + + let res = { + let conn = pool.get()?; + let res = g(conn).await; + self.in_use.remove(); + res + }; + res } /// Return `true` if a query in the SQL statement it executes returns one or more /// rows and false if the SQL returns an empty set. - pub fn exists

(&self, sql: &str, params: P) -> Result + pub async fn exists

(&self, sql: &str, params: P) -> Result where P: IntoIterator, P::Item: rusqlite::ToSql, { self.start_stmt(sql.to_string()); - self.with_conn(|conn| { + let res = { + let conn = self.get_conn().await?; let mut stmt = conn.prepare(sql)?; - let res = stmt.exists(params)?; - Ok(res) - }) + stmt.exists(params)? + }; + + Ok(res) } /// Execute a query which is expected to return one row. - pub fn query_row(&self, sql: impl AsRef, params: P, f: F) -> Result + pub async fn query_row(&self, sql: impl AsRef, params: P, f: F) -> Result where - P: IntoIterator, + P: IntoIterator + Copy, P::Item: rusqlite::ToSql, F: FnOnce(&rusqlite::Row) -> rusqlite::Result, { self.start_stmt(sql.as_ref().to_string()); - self.with_conn(|conn| conn.query_row(sql.as_ref(), params, f).map_err(Into::into)) + let sql = sql.as_ref(); + let res = { + let conn = self.get_conn().await?; + conn.query_row(sql, params, f)? + }; + + Ok(res) } - pub fn table_exists(&self, name: impl AsRef) -> bool { - self.with_conn(|conn| table_exists(conn, name)) - .unwrap_or_default() + pub async fn table_exists(&self, name: impl AsRef) -> Result { + self.start_stmt("table_exists"); + self.with_conn(|conn| { + let mut exists = false; + conn.pragma(None, "table_info", &name.as_ref().to_string(), |_row| { + // will only be executed if the info was found + exists = true; + Ok(()) + })?; + + Ok(exists) + }) + .await } /// Executes a query which is expected to return one row and one /// column. If the query does not return a value or returns SQL /// `NULL`, returns `Ok(None)`. - pub fn query_get_value_result(&self, query: &str, params: P) -> Result> + pub async fn query_get_value_result(&self, query: &str, params: P) -> Result> where - P: IntoIterator, + P: IntoIterator + Copy, P::Item: rusqlite::ToSql, T: rusqlite::types::FromSql, { - match self.query_row(query, params, |row| row.get::<_, T>(0)) { + match self + .query_row(query, params, |row| row.get::<_, T>(0)) + .await + { Ok(res) => Ok(Some(res)), Err(Error::Sql(rusqlite::Error::QueryReturnedNoRows)) => Ok(None), Err(Error::Sql(rusqlite::Error::InvalidColumnType( @@ -240,13 +299,18 @@ impl Sql { /// Not resultified version of `query_get_value_result`. Returns /// `None` on error. - pub fn query_get_value(&self, context: &Context, query: &str, params: P) -> Option + pub async fn query_get_value( + &self, + context: &Context, + query: &str, + params: P, + ) -> Option where - P: IntoIterator, + P: IntoIterator + Copy, P::Item: rusqlite::ToSql, T: rusqlite::types::FromSql, { - match self.query_get_value_result(query, params) { + match self.query_get_value_result(query, params).await { Ok(res) => res, Err(err) => { error!(context, "sql: Failed query_row: {}", err); @@ -259,42 +323,38 @@ impl Sql { /// /// Setting `None` deletes the value. On failure an error message /// will already have been logged. - pub fn set_raw_config( + pub async fn set_raw_config( &self, context: &Context, key: impl AsRef, value: Option<&str>, ) -> Result<()> { - if !self.is_open() { + if !self.is_open().await { error!(context, "set_raw_config(): Database not ready."); return Err(Error::SqlNoConnection); } let key = key.as_ref(); let res = if let Some(ref value) = value { - let exists = self.exists("SELECT value FROM config WHERE keyname=?;", params![key])?; + let exists = self + .exists("SELECT value FROM config WHERE keyname=?;", params![key]) + .await?; if exists { - execute( - context, - self, + self.execute( "UPDATE config SET value=? WHERE keyname=?;", params![value, key], ) + .await } else { - execute( - context, - self, + self.execute( "INSERT INTO config (keyname, value) VALUES (?, ?);", params![key, value], ) + .await } } else { - execute( - context, - self, - "DELETE FROM config WHERE keyname=?;", - params![key], - ) + self.execute("DELETE FROM config WHERE keyname=?;", params![key]) + .await }; match res { @@ -307,8 +367,8 @@ impl Sql { } /// Get configuration options from the database. - pub fn get_raw_config(&self, context: &Context, key: impl AsRef) -> Option { - if !self.is_open() || key.as_ref().is_empty() { + pub async fn get_raw_config(&self, context: &Context, key: impl AsRef) -> Option { + if !self.is_open().await || key.as_ref().is_empty() { return None; } self.query_get_value( @@ -316,47 +376,59 @@ impl Sql { "SELECT value FROM config WHERE keyname=?;", params![key.as_ref()], ) + .await } - pub fn set_raw_config_int( + pub async fn set_raw_config_int( &self, context: &Context, key: impl AsRef, value: i32, ) -> Result<()> { self.set_raw_config(context, key, Some(&format!("{}", value))) + .await } - pub fn get_raw_config_int(&self, context: &Context, key: impl AsRef) -> Option { + pub async fn get_raw_config_int(&self, context: &Context, key: impl AsRef) -> Option { self.get_raw_config(context, key) + .await .and_then(|s| s.parse().ok()) } - pub fn get_raw_config_bool(&self, context: &Context, key: impl AsRef) -> bool { + pub async fn get_raw_config_bool(&self, context: &Context, key: impl AsRef) -> bool { // Not the most obvious way to encode bool as string, but it is matter // of backward compatibility. - self.get_raw_config_int(context, key).unwrap_or_default() > 0 + self.get_raw_config_int(context, key) + .await + .unwrap_or_default() + > 0 } - pub fn set_raw_config_bool(&self, context: &Context, key: T, value: bool) -> Result<()> + pub async fn set_raw_config_bool(&self, context: &Context, key: T, value: bool) -> Result<()> where T: AsRef, { let value = if value { Some("1") } else { None }; - self.set_raw_config(context, key, value) + self.set_raw_config(context, key, value).await } - pub fn set_raw_config_int64( + pub async fn set_raw_config_int64( &self, context: &Context, key: impl AsRef, value: i64, ) -> Result<()> { self.set_raw_config(context, key, Some(&format!("{}", value))) + .await } - pub fn get_raw_config_int64(&self, context: &Context, key: impl AsRef) -> Option { + pub async fn get_raw_config_int64( + &self, + context: &Context, + key: impl AsRef, + ) -> Option { self.get_raw_config(context, key) + .await .and_then(|r| r.parse().ok()) } @@ -370,622 +442,60 @@ impl Sql { self.in_use.set(stmt.as_ref().to_string()); } -} -fn table_exists(conn: &Connection, name: impl AsRef) -> Result { - let mut exists = false; - conn.pragma(None, "table_info", &name.as_ref().to_string(), |_row| { - // will only be executed if the info was found - exists = true; - Ok(()) - })?; - Ok(exists) -} + /// Alternative to sqlite3_last_insert_rowid() which MUST NOT be used due to race conditions, see comment above. + /// the ORDER BY ensures, this function always returns the most recent id, + /// eg. if a Message-ID is split into different messages. + pub async fn get_rowid( + &self, + context: &Context, + table: impl AsRef, + field: impl AsRef, + value: impl AsRef, + ) -> Result { + self.start_stmt("get rowid".to_string()); -#[allow(clippy::cognitive_complexity)] -fn open( - context: &Context, - sql: &Sql, - dbfile: impl AsRef, - readonly: bool, -) -> crate::error::Result<()> { - if sql.is_open() { - error!( - context, - "Cannot open, database \"{:?}\" already opened.", - dbfile.as_ref(), + let query = format!( + "SELECT id FROM {} WHERE {}=? ORDER BY id DESC", + table.as_ref(), + field.as_ref(), ); - return Err(Error::SqlAlreadyOpen.into()); + + let res = { + let mut conn = self.get_conn().await?; + get_rowid(context, &mut conn, table, field, value)? + }; + + Ok(res) } - let mut open_flags = OpenFlags::SQLITE_OPEN_NO_MUTEX; - if readonly { - open_flags.insert(OpenFlags::SQLITE_OPEN_READ_ONLY); - } else { - open_flags.insert(OpenFlags::SQLITE_OPEN_READ_WRITE); - open_flags.insert(OpenFlags::SQLITE_OPEN_CREATE); - } - let mgr = r2d2_sqlite::SqliteConnectionManager::file(dbfile.as_ref()) - .with_flags(open_flags) - .with_init(|c| c.execute_batch("PRAGMA secure_delete=on;")); - let pool = r2d2::Pool::builder() - .min_idle(Some(2)) - .max_size(10) - .connection_timeout(std::time::Duration::new(60, 0)) - .build(mgr) - .map_err(Error::ConnectionPool)?; + pub async fn get_rowid2( + &self, + context: &Context, + table: impl AsRef, + field: impl AsRef, + value: i64, + field2: impl AsRef, + value2: i32, + ) -> Result { + self.start_stmt("get rowid2".to_string()); - { - *sql.pool.write().unwrap() = Some(pool); - } + let res = { + let mut conn = self.get_conn().await?; + get_rowid2(context, &mut conn, table, field, value, field2, value2)? + }; - if !readonly { - let mut exists_before_update = false; - let mut dbversion_before_update = 0; - /* Init tables to dbversion=0 */ - if !sql.table_exists("config") { - info!( - context, - "First time init: creating tables in {:?}.", - dbfile.as_ref(), - ); - sql.execute( - "CREATE TABLE config (id INTEGER PRIMARY KEY, keyname TEXT, value TEXT);", - NO_PARAMS, - )?; - sql.execute("CREATE INDEX config_index1 ON config (keyname);", NO_PARAMS)?; - sql.execute( - "CREATE TABLE contacts (\ - id INTEGER PRIMARY KEY AUTOINCREMENT, \ - name TEXT DEFAULT '', \ - addr TEXT DEFAULT '' COLLATE NOCASE, \ - origin INTEGER DEFAULT 0, \ - blocked INTEGER DEFAULT 0, \ - last_seen INTEGER DEFAULT 0, \ - param TEXT DEFAULT '');", - params![], - )?; - sql.execute( - "CREATE INDEX contacts_index1 ON contacts (name COLLATE NOCASE);", - params![], - )?; - sql.execute( - "CREATE INDEX contacts_index2 ON contacts (addr COLLATE NOCASE);", - params![], - )?; - sql.execute( - "INSERT INTO contacts (id,name,origin) VALUES \ - (1,'self',262144), (2,'info',262144), (3,'rsvd',262144), \ - (4,'rsvd',262144), (5,'device',262144), (6,'rsvd',262144), \ - (7,'rsvd',262144), (8,'rsvd',262144), (9,'rsvd',262144);", - params![], - )?; - sql.execute( - "CREATE TABLE chats (\ - id INTEGER PRIMARY KEY AUTOINCREMENT, \ - type INTEGER DEFAULT 0, \ - name TEXT DEFAULT '', \ - draft_timestamp INTEGER DEFAULT 0, \ - draft_txt TEXT DEFAULT '', \ - blocked INTEGER DEFAULT 0, \ - grpid TEXT DEFAULT '', \ - param TEXT DEFAULT '');", - params![], - )?; - sql.execute("CREATE INDEX chats_index1 ON chats (grpid);", params![])?; - sql.execute( - "CREATE TABLE chats_contacts (chat_id INTEGER, contact_id INTEGER);", - params![], - )?; - sql.execute( - "CREATE INDEX chats_contacts_index1 ON chats_contacts (chat_id);", - params![], - )?; - sql.execute( - "INSERT INTO chats (id,type,name) VALUES \ - (1,120,'deaddrop'), (2,120,'rsvd'), (3,120,'trash'), \ - (4,120,'msgs_in_creation'), (5,120,'starred'), (6,120,'archivedlink'), \ - (7,100,'rsvd'), (8,100,'rsvd'), (9,100,'rsvd');", - params![], - )?; - sql.execute( - "CREATE TABLE msgs (\ - id INTEGER PRIMARY KEY AUTOINCREMENT, \ - rfc724_mid TEXT DEFAULT '', \ - server_folder TEXT DEFAULT '', \ - server_uid INTEGER DEFAULT 0, \ - chat_id INTEGER DEFAULT 0, \ - from_id INTEGER DEFAULT 0, \ - to_id INTEGER DEFAULT 0, \ - timestamp INTEGER DEFAULT 0, \ - type INTEGER DEFAULT 0, \ - state INTEGER DEFAULT 0, \ - msgrmsg INTEGER DEFAULT 1, \ - bytes INTEGER DEFAULT 0, \ - txt TEXT DEFAULT '', \ - txt_raw TEXT DEFAULT '', \ - param TEXT DEFAULT '');", - params![], - )?; - sql.execute("CREATE INDEX msgs_index1 ON msgs (rfc724_mid);", params![])?; - sql.execute("CREATE INDEX msgs_index2 ON msgs (chat_id);", params![])?; - sql.execute("CREATE INDEX msgs_index3 ON msgs (timestamp);", params![])?; - sql.execute("CREATE INDEX msgs_index4 ON msgs (state);", params![])?; - sql.execute( - "INSERT INTO msgs (id,msgrmsg,txt) VALUES \ - (1,0,'marker1'), (2,0,'rsvd'), (3,0,'rsvd'), \ - (4,0,'rsvd'), (5,0,'rsvd'), (6,0,'rsvd'), (7,0,'rsvd'), \ - (8,0,'rsvd'), (9,0,'daymarker');", - params![], - )?; - sql.execute( - "CREATE TABLE jobs (\ - id INTEGER PRIMARY KEY AUTOINCREMENT, \ - added_timestamp INTEGER, \ - desired_timestamp INTEGER DEFAULT 0, \ - action INTEGER, \ - foreign_id INTEGER, \ - param TEXT DEFAULT '');", - params![], - )?; - sql.execute( - "CREATE INDEX jobs_index1 ON jobs (desired_timestamp);", - params![], - )?; - if !sql.table_exists("config") - || !sql.table_exists("contacts") - || !sql.table_exists("chats") - || !sql.table_exists("chats_contacts") - || !sql.table_exists("msgs") - || !sql.table_exists("jobs") - { - error!( - context, - "Cannot create tables in new database \"{:?}\".", - dbfile.as_ref(), - ); - // cannot create the tables - maybe we cannot write? - return Err(Error::SqlFailedToOpen.into()); - } else { - sql.set_raw_config_int(context, "dbversion", 0)?; - } - } else { - exists_before_update = true; - dbversion_before_update = sql - .get_raw_config_int(context, "dbversion") - .unwrap_or_default(); - } - - // (1) update low-level database structure. - // this should be done before updates that use high-level objects that - // rely themselves on the low-level structure. - // -------------------------------------------------------------------- - - let mut dbversion = dbversion_before_update; - let mut recalc_fingerprints = false; - let mut update_icons = false; - - if dbversion < 1 { - info!(context, "[migration] v1"); - sql.execute( - "CREATE TABLE leftgrps ( id INTEGER PRIMARY KEY, grpid TEXT DEFAULT '');", - params![], - )?; - sql.execute( - "CREATE INDEX leftgrps_index1 ON leftgrps (grpid);", - params![], - )?; - dbversion = 1; - sql.set_raw_config_int(context, "dbversion", 1)?; - } - if dbversion < 2 { - info!(context, "[migration] v2"); - sql.execute( - "ALTER TABLE contacts ADD COLUMN authname TEXT DEFAULT '';", - params![], - )?; - dbversion = 2; - sql.set_raw_config_int(context, "dbversion", 2)?; - } - if dbversion < 7 { - info!(context, "[migration] v7"); - sql.execute( - "CREATE TABLE keypairs (\ - id INTEGER PRIMARY KEY, \ - addr TEXT DEFAULT '' COLLATE NOCASE, \ - is_default INTEGER DEFAULT 0, \ - private_key, \ - public_key, \ - created INTEGER DEFAULT 0);", - params![], - )?; - dbversion = 7; - sql.set_raw_config_int(context, "dbversion", 7)?; - } - if dbversion < 10 { - info!(context, "[migration] v10"); - sql.execute( - "CREATE TABLE acpeerstates (\ - id INTEGER PRIMARY KEY, \ - addr TEXT DEFAULT '' COLLATE NOCASE, \ - last_seen INTEGER DEFAULT 0, \ - last_seen_autocrypt INTEGER DEFAULT 0, \ - public_key, \ - prefer_encrypted INTEGER DEFAULT 0);", - params![], - )?; - sql.execute( - "CREATE INDEX acpeerstates_index1 ON acpeerstates (addr);", - params![], - )?; - dbversion = 10; - sql.set_raw_config_int(context, "dbversion", 10)?; - } - if dbversion < 12 { - info!(context, "[migration] v12"); - sql.execute( - "CREATE TABLE msgs_mdns ( msg_id INTEGER, contact_id INTEGER);", - params![], - )?; - sql.execute( - "CREATE INDEX msgs_mdns_index1 ON msgs_mdns (msg_id);", - params![], - )?; - dbversion = 12; - sql.set_raw_config_int(context, "dbversion", 12)?; - } - if dbversion < 17 { - info!(context, "[migration] v17"); - sql.execute( - "ALTER TABLE chats ADD COLUMN archived INTEGER DEFAULT 0;", - params![], - )?; - sql.execute("CREATE INDEX chats_index2 ON chats (archived);", params![])?; - sql.execute( - "ALTER TABLE msgs ADD COLUMN starred INTEGER DEFAULT 0;", - params![], - )?; - sql.execute("CREATE INDEX msgs_index5 ON msgs (starred);", params![])?; - dbversion = 17; - sql.set_raw_config_int(context, "dbversion", 17)?; - } - if dbversion < 18 { - info!(context, "[migration] v18"); - sql.execute( - "ALTER TABLE acpeerstates ADD COLUMN gossip_timestamp INTEGER DEFAULT 0;", - params![], - )?; - sql.execute("ALTER TABLE acpeerstates ADD COLUMN gossip_key;", params![])?; - dbversion = 18; - sql.set_raw_config_int(context, "dbversion", 18)?; - } - if dbversion < 27 { - info!(context, "[migration] v27"); - // chat.id=1 and chat.id=2 are the old deaddrops, - // the current ones are defined by chats.blocked=2 - sql.execute("DELETE FROM msgs WHERE chat_id=1 OR chat_id=2;", params![])?; - sql.execute( - "CREATE INDEX chats_contacts_index2 ON chats_contacts (contact_id);", - params![], - )?; - sql.execute( - "ALTER TABLE msgs ADD COLUMN timestamp_sent INTEGER DEFAULT 0;", - params![], - )?; - sql.execute( - "ALTER TABLE msgs ADD COLUMN timestamp_rcvd INTEGER DEFAULT 0;", - params![], - )?; - dbversion = 27; - sql.set_raw_config_int(context, "dbversion", 27)?; - } - if dbversion < 34 { - info!(context, "[migration] v34"); - sql.execute( - "ALTER TABLE msgs ADD COLUMN hidden INTEGER DEFAULT 0;", - params![], - )?; - sql.execute( - "ALTER TABLE msgs_mdns ADD COLUMN timestamp_sent INTEGER DEFAULT 0;", - params![], - )?; - sql.execute( - "ALTER TABLE acpeerstates ADD COLUMN public_key_fingerprint TEXT DEFAULT '';", - params![], - )?; - sql.execute( - "ALTER TABLE acpeerstates ADD COLUMN gossip_key_fingerprint TEXT DEFAULT '';", - params![], - )?; - sql.execute( - "CREATE INDEX acpeerstates_index3 ON acpeerstates (public_key_fingerprint);", - params![], - )?; - sql.execute( - "CREATE INDEX acpeerstates_index4 ON acpeerstates (gossip_key_fingerprint);", - params![], - )?; - recalc_fingerprints = true; - dbversion = 34; - sql.set_raw_config_int(context, "dbversion", 34)?; - } - if dbversion < 39 { - info!(context, "[migration] v39"); - sql.execute( - "CREATE TABLE tokens ( id INTEGER PRIMARY KEY, namespc INTEGER DEFAULT 0, foreign_id INTEGER DEFAULT 0, token TEXT DEFAULT '', timestamp INTEGER DEFAULT 0);", - params![] - )?; - sql.execute( - "ALTER TABLE acpeerstates ADD COLUMN verified_key;", - params![], - )?; - sql.execute( - "ALTER TABLE acpeerstates ADD COLUMN verified_key_fingerprint TEXT DEFAULT '';", - params![], - )?; - sql.execute( - "CREATE INDEX acpeerstates_index5 ON acpeerstates (verified_key_fingerprint);", - params![], - )?; - dbversion = 39; - sql.set_raw_config_int(context, "dbversion", 39)?; - } - if dbversion < 40 { - info!(context, "[migration] v40"); - sql.execute( - "ALTER TABLE jobs ADD COLUMN thread INTEGER DEFAULT 0;", - params![], - )?; - dbversion = 40; - sql.set_raw_config_int(context, "dbversion", 40)?; - } - if dbversion < 44 { - info!(context, "[migration] v44"); - sql.execute("ALTER TABLE msgs ADD COLUMN mime_headers TEXT;", params![])?; - dbversion = 44; - sql.set_raw_config_int(context, "dbversion", 44)?; - } - if dbversion < 46 { - info!(context, "[migration] v46"); - sql.execute( - "ALTER TABLE msgs ADD COLUMN mime_in_reply_to TEXT;", - params![], - )?; - sql.execute( - "ALTER TABLE msgs ADD COLUMN mime_references TEXT;", - params![], - )?; - dbversion = 46; - sql.set_raw_config_int(context, "dbversion", 46)?; - } - if dbversion < 47 { - info!(context, "[migration] v47"); - sql.execute( - "ALTER TABLE jobs ADD COLUMN tries INTEGER DEFAULT 0;", - params![], - )?; - dbversion = 47; - sql.set_raw_config_int(context, "dbversion", 47)?; - } - if dbversion < 48 { - info!(context, "[migration] v48"); - // NOTE: move_state is not used anymore - sql.execute( - "ALTER TABLE msgs ADD COLUMN move_state INTEGER DEFAULT 1;", - params![], - )?; - - dbversion = 48; - sql.set_raw_config_int(context, "dbversion", 48)?; - } - if dbversion < 49 { - info!(context, "[migration] v49"); - sql.execute( - "ALTER TABLE chats ADD COLUMN gossiped_timestamp INTEGER DEFAULT 0;", - params![], - )?; - dbversion = 49; - sql.set_raw_config_int(context, "dbversion", 49)?; - } - if dbversion < 50 { - info!(context, "[migration] v50"); - // installations <= 0.100.1 used DC_SHOW_EMAILS_ALL implicitly; - // keep this default and use DC_SHOW_EMAILS_NO - // only for new installations - if exists_before_update { - sql.set_raw_config_int(context, "show_emails", ShowEmails::All as i32)?; - } - dbversion = 50; - sql.set_raw_config_int(context, "dbversion", 50)?; - } - if dbversion < 53 { - info!(context, "[migration] v53"); - // the messages containing _only_ locations - // are also added to the database as _hidden_. - sql.execute( - "CREATE TABLE locations ( id INTEGER PRIMARY KEY AUTOINCREMENT, latitude REAL DEFAULT 0.0, longitude REAL DEFAULT 0.0, accuracy REAL DEFAULT 0.0, timestamp INTEGER DEFAULT 0, chat_id INTEGER DEFAULT 0, from_id INTEGER DEFAULT 0);", - params![] - )?; - sql.execute( - "CREATE INDEX locations_index1 ON locations (from_id);", - params![], - )?; - sql.execute( - "CREATE INDEX locations_index2 ON locations (timestamp);", - params![], - )?; - sql.execute( - "ALTER TABLE chats ADD COLUMN locations_send_begin INTEGER DEFAULT 0;", - params![], - )?; - sql.execute( - "ALTER TABLE chats ADD COLUMN locations_send_until INTEGER DEFAULT 0;", - params![], - )?; - sql.execute( - "ALTER TABLE chats ADD COLUMN locations_last_sent INTEGER DEFAULT 0;", - params![], - )?; - sql.execute( - "CREATE INDEX chats_index3 ON chats (locations_send_until);", - params![], - )?; - dbversion = 53; - sql.set_raw_config_int(context, "dbversion", 53)?; - } - if dbversion < 54 { - info!(context, "[migration] v54"); - sql.execute( - "ALTER TABLE msgs ADD COLUMN location_id INTEGER DEFAULT 0;", - params![], - )?; - sql.execute("CREATE INDEX msgs_index6 ON msgs (location_id);", params![])?; - dbversion = 54; - sql.set_raw_config_int(context, "dbversion", 54)?; - } - if dbversion < 55 { - info!(context, "[migration] v55"); - sql.execute( - "ALTER TABLE locations ADD COLUMN independent INTEGER DEFAULT 0;", - params![], - )?; - sql.set_raw_config_int(context, "dbversion", 55)?; - } - if dbversion < 59 { - info!(context, "[migration] v59"); - // records in the devmsglabels are kept when the message is deleted. - // so, msg_id may or may not exist. - sql.execute( - "CREATE TABLE devmsglabels (id INTEGER PRIMARY KEY AUTOINCREMENT, label TEXT, msg_id INTEGER DEFAULT 0);", - NO_PARAMS, - )?; - sql.execute( - "CREATE INDEX devmsglabels_index1 ON devmsglabels (label);", - NO_PARAMS, - )?; - if exists_before_update && sql.get_raw_config_int(context, "bcc_self").is_none() { - sql.set_raw_config_int(context, "bcc_self", 1)?; - } - sql.set_raw_config_int(context, "dbversion", 59)?; - } - if dbversion < 60 { - info!(context, "[migration] v60"); - sql.execute( - "ALTER TABLE chats ADD COLUMN created_timestamp INTEGER DEFAULT 0;", - NO_PARAMS, - )?; - sql.set_raw_config_int(context, "dbversion", 60)?; - } - if dbversion < 61 { - info!(context, "[migration] v61"); - sql.execute( - "ALTER TABLE contacts ADD COLUMN selfavatar_sent INTEGER DEFAULT 0;", - NO_PARAMS, - )?; - update_icons = true; - sql.set_raw_config_int(context, "dbversion", 61)?; - } - if dbversion < 62 { - info!(context, "[migration] v62"); - sql.execute( - "ALTER TABLE chats ADD COLUMN muted_until INTEGER DEFAULT 0;", - NO_PARAMS, - )?; - sql.set_raw_config_int(context, "dbversion", 62)?; - } - if dbversion < 63 { - info!(context, "[migration] v63"); - sql.execute("UPDATE chats SET grpid='' WHERE type=100", NO_PARAMS)?; - sql.set_raw_config_int(context, "dbversion", 63)?; - } - - // (2) updates that require high-level objects - // (the structure is complete now and all objects are usable) - // -------------------------------------------------------------------- - - if recalc_fingerprints { - info!(context, "[migration] recalc fingerprints"); - sql.query_map( - "SELECT addr FROM acpeerstates;", - params![], - |row| row.get::<_, String>(0), - |addrs| { - for addr in addrs { - if let Some(ref mut peerstate) = Peerstate::from_addr(context, sql, &addr?) - { - peerstate.recalc_fingerprint(); - peerstate.save_to_db(sql, false)?; - } - } - Ok(()) - }, - )?; - } - if update_icons { - update_saved_messages_icon(context)?; - update_device_icon(context)?; - } - } - - info!(context, "Opened {:?}.", dbfile.as_ref(),); - - Ok(()) -} - -pub fn execute

(context: &Context, sql: &Sql, querystr: impl AsRef, params: P) -> Result<()> -where - P: IntoIterator, - P::Item: rusqlite::ToSql, -{ - match sql.execute(querystr.as_ref(), params) { - Ok(_) => Ok(()), - Err(err) => { - error!( - context, - "execute failed: {:?} for {}", - &err, - querystr.as_ref() - ); - Err(err) - } - } -} - -pub fn try_execute(context: &Context, sql: &Sql, querystr: impl AsRef) -> Result<()> { - // same as execute() but does not pass error to ui - match sql.execute(querystr.as_ref(), params![]) { - Ok(_) => Ok(()), - Err(err) => { - warn!( - context, - "Try-execute for \"{}\" failed: {}", - querystr.as_ref(), - &err, - ); - Err(err) - } + Ok(res) } } pub fn get_rowid( context: &Context, - sql: &Sql, + conn: &mut Connection, table: impl AsRef, field: impl AsRef, value: impl AsRef, -) -> u32 { - sql.start_stmt("get rowid".to_string()); - sql.with_conn(|conn| Ok(get_rowid_with_conn(context, conn, table, field, value))) - .unwrap_or_else(|_| 0) -} - -pub fn get_rowid_with_conn( - context: &Context, - conn: &Connection, - table: impl AsRef, - field: impl AsRef, - value: impl AsRef, -) -> u32 { +) -> std::result::Result { // alternative to sqlite3_last_insert_rowid() which MUST NOT be used due to race conditions, see comment above. // the ORDER BY ensures, this function always returns the most recent id, // eg. if a Message-ID is split into different messages. @@ -995,45 +505,19 @@ pub fn get_rowid_with_conn( field.as_ref(), ); - match conn.query_row(&query, params![value.as_ref()], |row| row.get::<_, u32>(0)) { - Ok(id) => id, - Err(err) => { - error!( - context, - "sql: Failed to retrieve rowid: {} in {}", err, query - ); - 0 - } - } -} -pub fn get_rowid2( - context: &Context, - sql: &Sql, - table: impl AsRef, - field: impl AsRef, - value: i64, - field2: impl AsRef, - value2: i32, -) -> u32 { - sql.start_stmt("get rowid2".to_string()); - sql.with_conn(|conn| { - Ok(get_rowid2_with_conn( - context, conn, table, field, value, field2, value2, - )) - }) - .unwrap_or_else(|_| 0) + conn.query_row(&query, params![value.as_ref()], |row| row.get::<_, u32>(0)) } -pub fn get_rowid2_with_conn( +pub fn get_rowid2( context: &Context, - conn: &Connection, + conn: &mut Connection, table: impl AsRef, field: impl AsRef, value: i64, field2: impl AsRef, value2: i32, -) -> u32 { - match conn.query_row( +) -> std::result::Result { + conn.query_row( &format!( "SELECT id FROM {} WHERE {}={} AND {}={} ORDER BY id DESC", table.as_ref(), @@ -1044,16 +528,10 @@ pub fn get_rowid2_with_conn( ), NO_PARAMS, |row| row.get::<_, u32>(0), - ) { - Ok(id) => id, - Err(err) => { - error!(context, "sql: Failed to retrieve rowid2: {}", err); - 0 - } - } + ) } -pub fn housekeeping(context: &Context) { +pub async fn housekeeping(context: &Context) { let mut files_in_use = HashSet::new(); let mut unreferenced_count = 0; @@ -1063,25 +541,29 @@ pub fn housekeeping(context: &Context) { &mut files_in_use, "SELECT param FROM msgs WHERE chat_id!=3 AND type!=10;", Param::File, - ); + ) + .await; maybe_add_from_param( context, &mut files_in_use, "SELECT param FROM jobs;", Param::File, - ); + ) + .await; maybe_add_from_param( context, &mut files_in_use, "SELECT param FROM chats;", Param::ProfileImage, - ); + ) + .await; maybe_add_from_param( context, &mut files_in_use, "SELECT param FROM contacts;", Param::ProfileImage, - ); + ) + .await; context .sql @@ -1096,6 +578,7 @@ pub fn housekeeping(context: &Context) { Ok(()) }, ) + .await .unwrap_or_else(|err| { warn!(context, "sql: failed query: {}", err); }); @@ -1103,13 +586,13 @@ pub fn housekeeping(context: &Context) { info!(context, "{} files in use.", files_in_use.len(),); /* go through directory and delete unused files */ let p = context.get_blobdir(); - match std::fs::read_dir(p) { - Ok(dir_handle) => { + match async_std::fs::read_dir(p).await { + Ok(mut dir_handle) => { /* avoid deletion of files that are just created to build a message object */ let diff = std::time::Duration::from_secs(60 * 60); let keep_files_newer_than = std::time::SystemTime::now().checked_sub(diff).unwrap(); - for entry in dir_handle { + while let Some(entry) = dir_handle.next().await { if entry.is_err() { break; } @@ -1127,7 +610,7 @@ pub fn housekeeping(context: &Context) { unreferenced_count += 1; - if let Ok(stats) = std::fs::metadata(entry.path()) { + if let Ok(stats) = async_std::fs::metadata(entry.path()).await { let recently_created = stats.created().is_ok() && stats.created().unwrap() > keep_files_newer_than; let recently_modified = stats.modified().is_ok() @@ -1190,7 +673,7 @@ fn maybe_add_file(files_in_use: &mut HashSet, file: impl AsRef) { files_in_use.insert(file.as_ref()[9..].into()); } -fn maybe_add_from_param( +async fn maybe_add_from_param( context: &Context, files_in_use: &mut HashSet, query: &str, @@ -1212,11 +695,644 @@ fn maybe_add_from_param( Ok(()) }, ) + .await .unwrap_or_else(|err| { warn!(context, "sql: failed to add_from_param: {}", err); }); } +#[allow(clippy::cognitive_complexity)] +async fn open( + context: &Context, + sql: &Sql, + dbfile: impl AsRef, + readonly: bool, +) -> crate::error::Result<()> { + if sql.is_open().await { + error!( + context, + "Cannot open, database \"{:?}\" already opened.", + dbfile.as_ref(), + ); + return Err(Error::SqlAlreadyOpen.into()); + } + + let mut open_flags = OpenFlags::SQLITE_OPEN_NO_MUTEX; + if readonly { + open_flags.insert(OpenFlags::SQLITE_OPEN_READ_ONLY); + } else { + open_flags.insert(OpenFlags::SQLITE_OPEN_READ_WRITE); + open_flags.insert(OpenFlags::SQLITE_OPEN_CREATE); + } + let mgr = r2d2_sqlite::SqliteConnectionManager::file(dbfile.as_ref()) + .with_flags(open_flags) + .with_init(|c| { + // Only one process can make changes to the database at one time. + // busy_timeout defines, that if a second process wants write access, + // this second process will wait some milliseconds + // and try over until it gets write access or the given timeout is elapsed. + // If the second process does not get write access within the given timeout, + // sqlite3_step() will return the error SQLITE_BUSY. + // (without a busy_timeout, sqlite3_step() would return SQLITE_BUSY _at once_) + c.busy_timeout(Duration::from_secs(10))?; + + c.execute_batch("PRAGMA secure_delete=on;")?; + Ok(()) + }); + let pool = r2d2::Pool::builder() + .min_idle(Some(2)) + .max_size(10) + .connection_timeout(std::time::Duration::new(60, 0)) + .build(mgr) + .map_err(Error::ConnectionPool)?; + + { + *sql.pool.write().await = Some(pool); + } + + if !readonly { + let mut exists_before_update = false; + let mut dbversion_before_update: i32 = 0; + /* Init tables to dbversion=0 */ + if !sql.table_exists("config").await? { + info!( + context, + "First time init: creating tables in {:?}.", + dbfile.as_ref(), + ); + sql.execute( + "CREATE TABLE config (id INTEGER PRIMARY KEY, keyname TEXT, value TEXT);", + NO_PARAMS, + ) + .await?; + sql.execute("CREATE INDEX config_index1 ON config (keyname);", NO_PARAMS) + .await?; + sql.execute( + "CREATE TABLE contacts (\ + id INTEGER PRIMARY KEY AUTOINCREMENT, \ + name TEXT DEFAULT '', \ + addr TEXT DEFAULT '' COLLATE NOCASE, \ + origin INTEGER DEFAULT 0, \ + blocked INTEGER DEFAULT 0, \ + last_seen INTEGER DEFAULT 0, \ + param TEXT DEFAULT '');", + params![], + ) + .await?; + sql.execute( + "CREATE INDEX contacts_index1 ON contacts (name COLLATE NOCASE);", + params![], + ) + .await?; + sql.execute( + "CREATE INDEX contacts_index2 ON contacts (addr COLLATE NOCASE);", + params![], + ) + .await?; + sql.execute( + "INSERT INTO contacts (id,name,origin) VALUES \ + (1,'self',262144), (2,'info',262144), (3,'rsvd',262144), \ + (4,'rsvd',262144), (5,'device',262144), (6,'rsvd',262144), \ + (7,'rsvd',262144), (8,'rsvd',262144), (9,'rsvd',262144);", + params![], + ) + .await?; + sql.execute( + "CREATE TABLE chats (\ + id INTEGER PRIMARY KEY AUTOINCREMENT, \ + type INTEGER DEFAULT 0, \ + name TEXT DEFAULT '', \ + draft_timestamp INTEGER DEFAULT 0, \ + draft_txt TEXT DEFAULT '', \ + blocked INTEGER DEFAULT 0, \ + grpid TEXT DEFAULT '', \ + param TEXT DEFAULT '');", + params![], + ) + .await?; + sql.execute("CREATE INDEX chats_index1 ON chats (grpid);", params![]) + .await?; + sql.execute( + "CREATE TABLE chats_contacts (chat_id INTEGER, contact_id INTEGER);", + params![], + ) + .await?; + sql.execute( + "CREATE INDEX chats_contacts_index1 ON chats_contacts (chat_id);", + params![], + ) + .await?; + sql.execute( + "INSERT INTO chats (id,type,name) VALUES \ + (1,120,'deaddrop'), (2,120,'rsvd'), (3,120,'trash'), \ + (4,120,'msgs_in_creation'), (5,120,'starred'), (6,120,'archivedlink'), \ + (7,100,'rsvd'), (8,100,'rsvd'), (9,100,'rsvd');", + params![], + ) + .await?; + sql.execute( + "CREATE TABLE msgs (\ + id INTEGER PRIMARY KEY AUTOINCREMENT, \ + rfc724_mid TEXT DEFAULT '', \ + server_folder TEXT DEFAULT '', \ + server_uid INTEGER DEFAULT 0, \ + chat_id INTEGER DEFAULT 0, \ + from_id INTEGER DEFAULT 0, \ + to_id INTEGER DEFAULT 0, \ + timestamp INTEGER DEFAULT 0, \ + type INTEGER DEFAULT 0, \ + state INTEGER DEFAULT 0, \ + msgrmsg INTEGER DEFAULT 1, \ + bytes INTEGER DEFAULT 0, \ + txt TEXT DEFAULT '', \ + txt_raw TEXT DEFAULT '', \ + param TEXT DEFAULT '');", + params![], + ) + .await?; + sql.execute("CREATE INDEX msgs_index1 ON msgs (rfc724_mid);", params![]) + .await?; + sql.execute("CREATE INDEX msgs_index2 ON msgs (chat_id);", params![]) + .await?; + sql.execute("CREATE INDEX msgs_index3 ON msgs (timestamp);", params![]) + .await?; + sql.execute("CREATE INDEX msgs_index4 ON msgs (state);", params![]) + .await?; + sql.execute( + "INSERT INTO msgs (id,msgrmsg,txt) VALUES \ + (1,0,'marker1'), (2,0,'rsvd'), (3,0,'rsvd'), \ + (4,0,'rsvd'), (5,0,'rsvd'), (6,0,'rsvd'), (7,0,'rsvd'), \ + (8,0,'rsvd'), (9,0,'daymarker');", + params![], + ) + .await?; + sql.execute( + "CREATE TABLE jobs (\ + id INTEGER PRIMARY KEY AUTOINCREMENT, \ + added_timestamp INTEGER, \ + desired_timestamp INTEGER DEFAULT 0, \ + action INTEGER, \ + foreign_id INTEGER, \ + param TEXT DEFAULT '');", + params![], + ) + .await?; + sql.execute( + "CREATE INDEX jobs_index1 ON jobs (desired_timestamp);", + params![], + ) + .await?; + if !sql.table_exists("config").await? + || !sql.table_exists("contacts").await? + || !sql.table_exists("chats").await? + || !sql.table_exists("chats_contacts").await? + || !sql.table_exists("msgs").await? + || !sql.table_exists("jobs").await? + { + error!( + context, + "Cannot create tables in new database \"{:?}\".", + dbfile.as_ref(), + ); + // cannot create the tables - maybe we cannot write? + return Err(Error::SqlFailedToOpen.into()); + } else { + sql.set_raw_config_int(context, "dbversion", 0).await?; + } + } else { + exists_before_update = true; + dbversion_before_update = sql + .get_raw_config_int(context, "dbversion") + .await + .unwrap_or_default(); + } + + // (1) update low-level database structure. + // this should be done before updates that use high-level objects that + // rely themselves on the low-level structure. + // -------------------------------------------------------------------- + + let mut dbversion = dbversion_before_update; + let mut recalc_fingerprints = false; + let mut update_icons = false; + + if dbversion < 1 { + info!(context, "[migration] v1"); + sql.execute( + "CREATE TABLE leftgrps ( id INTEGER PRIMARY KEY, grpid TEXT DEFAULT '');", + params![], + ) + .await?; + sql.execute( + "CREATE INDEX leftgrps_index1 ON leftgrps (grpid);", + params![], + ) + .await?; + dbversion = 1; + sql.set_raw_config_int(context, "dbversion", 1).await?; + } + if dbversion < 2 { + info!(context, "[migration] v2"); + sql.execute( + "ALTER TABLE contacts ADD COLUMN authname TEXT DEFAULT '';", + params![], + ) + .await?; + dbversion = 2; + sql.set_raw_config_int(context, "dbversion", 2).await?; + } + if dbversion < 7 { + info!(context, "[migration] v7"); + sql.execute( + "CREATE TABLE keypairs (\ + id INTEGER PRIMARY KEY, \ + addr TEXT DEFAULT '' COLLATE NOCASE, \ + is_default INTEGER DEFAULT 0, \ + private_key, \ + public_key, \ + created INTEGER DEFAULT 0);", + params![], + ) + .await?; + dbversion = 7; + sql.set_raw_config_int(context, "dbversion", 7).await?; + } + if dbversion < 10 { + info!(context, "[migration] v10"); + sql.execute( + "CREATE TABLE acpeerstates (\ + id INTEGER PRIMARY KEY, \ + addr TEXT DEFAULT '' COLLATE NOCASE, \ + last_seen INTEGER DEFAULT 0, \ + last_seen_autocrypt INTEGER DEFAULT 0, \ + public_key, \ + prefer_encrypted INTEGER DEFAULT 0);", + params![], + ) + .await?; + sql.execute( + "CREATE INDEX acpeerstates_index1 ON acpeerstates (addr);", + params![], + ) + .await?; + dbversion = 10; + sql.set_raw_config_int(context, "dbversion", 10).await?; + } + if dbversion < 12 { + info!(context, "[migration] v12"); + sql.execute( + "CREATE TABLE msgs_mdns ( msg_id INTEGER, contact_id INTEGER);", + params![], + ) + .await?; + sql.execute( + "CREATE INDEX msgs_mdns_index1 ON msgs_mdns (msg_id);", + params![], + ) + .await?; + dbversion = 12; + sql.set_raw_config_int(context, "dbversion", 12).await?; + } + if dbversion < 17 { + info!(context, "[migration] v17"); + sql.execute( + "ALTER TABLE chats ADD COLUMN archived INTEGER DEFAULT 0;", + params![], + ) + .await?; + sql.execute("CREATE INDEX chats_index2 ON chats (archived);", params![]) + .await?; + sql.execute( + "ALTER TABLE msgs ADD COLUMN starred INTEGER DEFAULT 0;", + params![], + ) + .await?; + sql.execute("CREATE INDEX msgs_index5 ON msgs (starred);", params![]) + .await?; + dbversion = 17; + sql.set_raw_config_int(context, "dbversion", 17).await?; + } + if dbversion < 18 { + info!(context, "[migration] v18"); + sql.execute( + "ALTER TABLE acpeerstates ADD COLUMN gossip_timestamp INTEGER DEFAULT 0;", + params![], + ) + .await?; + sql.execute("ALTER TABLE acpeerstates ADD COLUMN gossip_key;", params![]) + .await?; + dbversion = 18; + sql.set_raw_config_int(context, "dbversion", 18).await?; + } + if dbversion < 27 { + info!(context, "[migration] v27"); + // chat.id=1 and chat.id=2 are the old deaddrops, + // the current ones are defined by chats.blocked=2 + sql.execute("DELETE FROM msgs WHERE chat_id=1 OR chat_id=2;", params![]) + .await?; + sql.execute( + "CREATE INDEX chats_contacts_index2 ON chats_contacts (contact_id);", + params![], + ) + .await?; + sql.execute( + "ALTER TABLE msgs ADD COLUMN timestamp_sent INTEGER DEFAULT 0;", + params![], + ) + .await?; + sql.execute( + "ALTER TABLE msgs ADD COLUMN timestamp_rcvd INTEGER DEFAULT 0;", + params![], + ) + .await?; + dbversion = 27; + sql.set_raw_config_int(context, "dbversion", 27).await?; + } + if dbversion < 34 { + info!(context, "[migration] v34"); + sql.execute( + "ALTER TABLE msgs ADD COLUMN hidden INTEGER DEFAULT 0;", + params![], + ) + .await?; + sql.execute( + "ALTER TABLE msgs_mdns ADD COLUMN timestamp_sent INTEGER DEFAULT 0;", + params![], + ) + .await?; + sql.execute( + "ALTER TABLE acpeerstates ADD COLUMN public_key_fingerprint TEXT DEFAULT '';", + params![], + ) + .await?; + sql.execute( + "ALTER TABLE acpeerstates ADD COLUMN gossip_key_fingerprint TEXT DEFAULT '';", + params![], + ) + .await?; + sql.execute( + "CREATE INDEX acpeerstates_index3 ON acpeerstates (public_key_fingerprint);", + params![], + ) + .await?; + sql.execute( + "CREATE INDEX acpeerstates_index4 ON acpeerstates (gossip_key_fingerprint);", + params![], + ) + .await?; + recalc_fingerprints = true; + dbversion = 34; + sql.set_raw_config_int(context, "dbversion", 34).await?; + } + if dbversion < 39 { + info!(context, "[migration] v39"); + sql.execute( + "CREATE TABLE tokens ( id INTEGER PRIMARY KEY, namespc INTEGER DEFAULT 0, foreign_id INTEGER DEFAULT 0, token TEXT DEFAULT '', timestamp INTEGER DEFAULT 0);", + params![] + ).await?; + sql.execute( + "ALTER TABLE acpeerstates ADD COLUMN verified_key;", + params![], + ) + .await?; + sql.execute( + "ALTER TABLE acpeerstates ADD COLUMN verified_key_fingerprint TEXT DEFAULT '';", + params![], + ) + .await?; + sql.execute( + "CREATE INDEX acpeerstates_index5 ON acpeerstates (verified_key_fingerprint);", + params![], + ) + .await?; + dbversion = 39; + sql.set_raw_config_int(context, "dbversion", 39).await?; + } + if dbversion < 40 { + info!(context, "[migration] v40"); + sql.execute( + "ALTER TABLE jobs ADD COLUMN thread INTEGER DEFAULT 0;", + params![], + ) + .await?; + dbversion = 40; + sql.set_raw_config_int(context, "dbversion", 40).await?; + } + if dbversion < 44 { + info!(context, "[migration] v44"); + sql.execute("ALTER TABLE msgs ADD COLUMN mime_headers TEXT;", params![]) + .await?; + dbversion = 44; + sql.set_raw_config_int(context, "dbversion", 44).await?; + } + if dbversion < 46 { + info!(context, "[migration] v46"); + sql.execute( + "ALTER TABLE msgs ADD COLUMN mime_in_reply_to TEXT;", + params![], + ) + .await?; + sql.execute( + "ALTER TABLE msgs ADD COLUMN mime_references TEXT;", + params![], + ) + .await?; + dbversion = 46; + sql.set_raw_config_int(context, "dbversion", 46).await?; + } + if dbversion < 47 { + info!(context, "[migration] v47"); + sql.execute( + "ALTER TABLE jobs ADD COLUMN tries INTEGER DEFAULT 0;", + params![], + ) + .await?; + dbversion = 47; + sql.set_raw_config_int(context, "dbversion", 47).await?; + } + if dbversion < 48 { + info!(context, "[migration] v48"); + // NOTE: move_state is not used anymore + sql.execute( + "ALTER TABLE msgs ADD COLUMN move_state INTEGER DEFAULT 1;", + params![], + ) + .await?; + + dbversion = 48; + sql.set_raw_config_int(context, "dbversion", 48).await?; + } + if dbversion < 49 { + info!(context, "[migration] v49"); + sql.execute( + "ALTER TABLE chats ADD COLUMN gossiped_timestamp INTEGER DEFAULT 0;", + params![], + ) + .await?; + dbversion = 49; + sql.set_raw_config_int(context, "dbversion", 49).await?; + } + if dbversion < 50 { + info!(context, "[migration] v50"); + // installations <= 0.100.1 used DC_SHOW_EMAILS_ALL implicitly; + // keep this default and use DC_SHOW_EMAILS_NO + // only for new installations + if exists_before_update { + sql.set_raw_config_int(context, "show_emails", ShowEmails::All as i32) + .await?; + } + dbversion = 50; + sql.set_raw_config_int(context, "dbversion", 50).await?; + } + if dbversion < 53 { + info!(context, "[migration] v53"); + // the messages containing _only_ locations + // are also added to the database as _hidden_. + sql.execute( + "CREATE TABLE locations ( id INTEGER PRIMARY KEY AUTOINCREMENT, latitude REAL DEFAULT 0.0, longitude REAL DEFAULT 0.0, accuracy REAL DEFAULT 0.0, timestamp INTEGER DEFAULT 0, chat_id INTEGER DEFAULT 0, from_id INTEGER DEFAULT 0);", + params![] + ).await?; + sql.execute( + "CREATE INDEX locations_index1 ON locations (from_id);", + params![], + ) + .await?; + sql.execute( + "CREATE INDEX locations_index2 ON locations (timestamp);", + params![], + ) + .await?; + sql.execute( + "ALTER TABLE chats ADD COLUMN locations_send_begin INTEGER DEFAULT 0;", + params![], + ) + .await?; + sql.execute( + "ALTER TABLE chats ADD COLUMN locations_send_until INTEGER DEFAULT 0;", + params![], + ) + .await?; + sql.execute( + "ALTER TABLE chats ADD COLUMN locations_last_sent INTEGER DEFAULT 0;", + params![], + ) + .await?; + sql.execute( + "CREATE INDEX chats_index3 ON chats (locations_send_until);", + params![], + ) + .await?; + dbversion = 53; + sql.set_raw_config_int(context, "dbversion", 53).await?; + } + if dbversion < 54 { + info!(context, "[migration] v54"); + sql.execute( + "ALTER TABLE msgs ADD COLUMN location_id INTEGER DEFAULT 0;", + params![], + ) + .await?; + sql.execute("CREATE INDEX msgs_index6 ON msgs (location_id);", params![]) + .await?; + dbversion = 54; + sql.set_raw_config_int(context, "dbversion", 54).await?; + } + if dbversion < 55 { + info!(context, "[migration] v55"); + sql.execute( + "ALTER TABLE locations ADD COLUMN independent INTEGER DEFAULT 0;", + params![], + ) + .await?; + sql.set_raw_config_int(context, "dbversion", 55).await?; + } + if dbversion < 59 { + info!(context, "[migration] v59"); + // records in the devmsglabels are kept when the message is deleted. + // so, msg_id may or may not exist. + sql.execute( + "CREATE TABLE devmsglabels (id INTEGER PRIMARY KEY AUTOINCREMENT, label TEXT, msg_id INTEGER DEFAULT 0);", + NO_PARAMS, + ).await?; + sql.execute( + "CREATE INDEX devmsglabels_index1 ON devmsglabels (label);", + NO_PARAMS, + ) + .await?; + if exists_before_update && sql.get_raw_config_int(context, "bcc_self").await.is_none() { + sql.set_raw_config_int(context, "bcc_self", 1).await?; + } + sql.set_raw_config_int(context, "dbversion", 59).await?; + } + if dbversion < 60 { + info!(context, "[migration] v60"); + sql.execute( + "ALTER TABLE chats ADD COLUMN created_timestamp INTEGER DEFAULT 0;", + NO_PARAMS, + ) + .await?; + sql.set_raw_config_int(context, "dbversion", 60).await?; + } + if dbversion < 61 { + info!(context, "[migration] v61"); + sql.execute( + "ALTER TABLE contacts ADD COLUMN selfavatar_sent INTEGER DEFAULT 0;", + NO_PARAMS, + ) + .await?; + update_icons = true; + sql.set_raw_config_int(context, "dbversion", 61).await?; + } + if dbversion < 62 { + info!(context, "[migration] v62"); + sql.execute( + "ALTER TABLE chats ADD COLUMN muted_until INTEGER DEFAULT 0;", + NO_PARAMS, + ) + .await?; + sql.set_raw_config_int(context, "dbversion", 62).await?; + } + if dbversion < 63 { + info!(context, "[migration] v63"); + sql.execute("UPDATE chats SET grpid='' WHERE type=100", NO_PARAMS) + .await?; + sql.set_raw_config_int(context, "dbversion", 63).await?; + } + + // (2) updates that require high-level objects + // (the structure is complete now and all objects are usable) + // -------------------------------------------------------------------- + + if recalc_fingerprints { + info!(context, "[migration] recalc fingerprints"); + sql.query_map_async( + "SELECT addr FROM acpeerstates;", + params![], + |row| row.get::<_, String>(0), + |addrs| async move { + for addr in addrs { + if let Some(ref mut peerstate) = Peerstate::from_addr(context, sql, &addr?) + { + peerstate.recalc_fingerprint(); + peerstate.save_to_db(sql, false).await?; + } + } + Ok(()) + }, + ) + .await?; + } + if update_icons { + update_saved_messages_icon(context).await?; + update_device_icon(context).await?; + } + } + + info!(context, "Opened {:?}.", dbfile.as_ref(),); + + Ok(()) +} + #[cfg(test)] mod test { use super::*; diff --git a/src/stock.rs b/src/stock.rs index b67e8b220..8ce11cc33 100644 --- a/src/stock.rs +++ b/src/stock.rs @@ -544,17 +544,17 @@ mod tests { async fn test_update_device_chats() { let t = dummy_context(); t.ctx.update_device_chats().ok(); - let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap(); + let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); assert_eq!(chats.len(), 2); chats.get_chat_id(0).delete(&t.ctx).await.ok(); chats.get_chat_id(1).delete(&t.ctx).await.ok(); - let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap(); + let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); assert_eq!(chats.len(), 0); // a subsequent call to update_device_chats() must not re-add manally deleted messages or chats t.ctx.update_device_chats().ok(); - let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap(); + let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); assert_eq!(chats.len(), 0); } } diff --git a/src/test_utils.rs b/src/test_utils.rs index 2c134cc28..681f57fa1 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -83,6 +83,7 @@ pub(crate) async fn configure_alice_keypair(ctx: &Context) -> String { .await .unwrap(); key::store_self_keypair(&ctx, &keypair, key::KeyPairUse::Default) + .await .expect("Failed to save Alice's key"); keypair.addr.to_string() } diff --git a/src/token.rs b/src/token.rs index 11ae53f0c..ac2b4af02 100644 --- a/src/token.rs +++ b/src/token.rs @@ -9,7 +9,6 @@ use deltachat_derive::*; use crate::chat::ChatId; use crate::context::Context; use crate::dc_tools::*; -use crate::sql; /// Token namespace #[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)] @@ -28,37 +27,46 @@ impl Default for Namespace { /// Creates a new token and saves it into the database. /// Returns created token. -pub fn save(context: &Context, namespace: Namespace, foreign_id: ChatId) -> String { +pub async fn save(context: &Context, namespace: Namespace, foreign_id: ChatId) -> String { // foreign_id may be 0 let token = dc_create_id(); - sql::execute( - context, - &context.sql, - "INSERT INTO tokens (namespc, foreign_id, token, timestamp) VALUES (?, ?, ?, ?);", - params![namespace, foreign_id, &token, time()], - ) - .ok(); + context + .sql + .execute( + "INSERT INTO tokens (namespc, foreign_id, token, timestamp) VALUES (?, ?, ?, ?);", + params![namespace, foreign_id, &token, time()], + ) + .await + .ok(); token } -pub fn lookup(context: &Context, namespace: Namespace, foreign_id: ChatId) -> Option { - context.sql.query_get_value::<_, String>( - context, - "SELECT token FROM tokens WHERE namespc=? AND foreign_id=?;", - params![namespace, foreign_id], - ) +pub async fn lookup(context: &Context, namespace: Namespace, foreign_id: ChatId) -> Option { + context + .sql + .query_get_value::<_, String>( + context, + "SELECT token FROM tokens WHERE namespc=? AND foreign_id=?;", + params![namespace, foreign_id], + ) + .await } -pub fn lookup_or_new(context: &Context, namespace: Namespace, foreign_id: ChatId) -> String { - lookup(context, namespace, foreign_id).unwrap_or_else(|| save(context, namespace, foreign_id)) +pub async fn lookup_or_new(context: &Context, namespace: Namespace, foreign_id: ChatId) -> String { + if let Some(token) = lookup(context, namespace, foreign_id).await { + return token; + } + + save(context, namespace, foreign_id).await } -pub fn exists(context: &Context, namespace: Namespace, token: &str) -> bool { +pub async fn exists(context: &Context, namespace: Namespace, token: &str) -> bool { context .sql .exists( "SELECT id FROM tokens WHERE namespc=? AND token=?;", params![namespace, token], ) + .await .unwrap_or_default() } From 818e9211921eaec850ae38800cb268abf852a42e Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Sun, 8 Mar 2020 17:42:31 +0100 Subject: [PATCH 007/118] it compiles --- examples/simple.rs | 24 +-- src/blob.rs | 66 +++---- src/chat.rs | 340 +++++++++++++++++++----------------- src/chatlist.rs | 36 ++-- src/config.rs | 11 +- src/configure/mod.rs | 21 +-- src/contact.rs | 228 +++++++++++++----------- src/context.rs | 108 ++++++------ src/dc_receive_imf.rs | 390 +++++++++++++++++++++++------------------- src/dc_tools.rs | 44 ++--- src/e2ee.rs | 50 +++--- src/imap/mod.rs | 51 +++--- src/imap/session.rs | 15 +- src/imex.rs | 153 +++++++++-------- src/job.rs | 224 ++++++++++++------------ src/key.rs | 62 ++++--- src/keyring.rs | 2 +- src/lib.rs | 15 +- src/location.rs | 62 ++++--- src/message.rs | 349 ++++++++++++++++++++++--------------- src/mimefactory.rs | 200 +++++++++++++--------- src/mimeparser.rs | 251 +++++++++++++++------------ src/oauth2.rs | 61 ++++--- src/param.rs | 18 +- src/peerstate.rs | 69 ++++---- src/qr.rs | 154 +++++++++-------- src/securejoin.rs | 207 ++++++++++++---------- src/smtp/mod.rs | 2 +- src/sql.rs | 329 +++++++++++++++++------------------ src/stock.rs | 223 ++++++++++++++---------- src/test_utils.rs | 8 +- src/token.rs | 8 +- tests/stress.rs | 21 ++- 33 files changed, 2103 insertions(+), 1699 deletions(-) diff --git a/examples/simple.rs b/examples/simple.rs index 38b143fc5..2b2c6e2a4 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -36,10 +36,11 @@ async fn main() { let dir = tempdir().unwrap(); let dbfile = dir.path().join("db.sqlite"); println!("creating database {:?}", dbfile); - let ctx = - Context::new(Box::new(cb), "FakeOs".into(), dbfile).expect("Failed to create context"); + let ctx = Context::new(Box::new(cb), "FakeOs".into(), dbfile) + .await + .expect("Failed to create context"); let running = Arc::new(RwLock::new(true)); - let info = ctx.get_info(); + let info = ctx.get_info().await; let duration = time::Duration::from_millis(4000); println!("info: {:#?}", info); @@ -54,18 +55,19 @@ async fn main() { perform_inbox_fetch(&ctx1).await; if *r1.read().await { - // perform_inbox_idle(&ctx1).await; + perform_inbox_idle(&ctx1).await; } } } }); let r1 = running.clone(); + let ctx1 = ctx.clone(); let t2 = async_std::task::spawn(async move { while *r1.read().await { - // perform_smtp_jobs(&ctx1).await; + perform_smtp_jobs(&ctx1).await; if *r1.read().await { - // perform_smtp_idle(&ctx1).await; + perform_smtp_idle(&ctx1).await; } } }); @@ -85,17 +87,19 @@ async fn main() { async_std::task::sleep(duration).await; println!("sending a message"); - let contact_id = Contact::create(&ctx, "dignifiedquire", "dignifiedquire@gmail.com").unwrap(); - let chat_id = chat::create_by_contact_id(&ctx, contact_id).unwrap(); + let contact_id = Contact::create(&ctx, "dignifiedquire", "dignifiedquire@gmail.com") + .await + .unwrap(); + let chat_id = chat::create_by_contact_id(&ctx, contact_id).await.unwrap(); chat::send_text_msg(&ctx, chat_id, "Hi, here is my first message!".into()) .await .unwrap(); println!("fetching chats.."); - let chats = Chatlist::try_load(&ctx, 0, None, None).unwrap(); + let chats = Chatlist::try_load(&ctx, 0, None, None).await.unwrap(); for i in 0..chats.len() { - let summary = chats.get_summary(&ctx, 0, None); + let summary = chats.get_summary(&ctx, 0, None).await; let text1 = summary.get_text1(); let text2 = summary.get_text2(); println!("chat: {} - {:?} - {:?}", i, text1, text2,); diff --git a/src/blob.rs b/src/blob.rs index 70d5ff7a0..c43cff569 100644 --- a/src/blob.rs +++ b/src/blob.rs @@ -486,9 +486,9 @@ mod tests { use crate::test_utils::*; - #[test] - fn test_create() { - let t = dummy_context(); + #[async_std::test] + async fn test_create() { + let t = dummy_context().await; let blob = BlobObject::create(&t.ctx, "foo", b"hello").unwrap(); let fname = t.ctx.get_blobdir().join("foo"); let data = fs::read(fname).unwrap(); @@ -497,39 +497,39 @@ mod tests { assert_eq!(blob.to_abs_path(), t.ctx.get_blobdir().join("foo")); } - #[test] - fn test_lowercase_ext() { - let t = dummy_context(); + #[async_std::test] + async fn test_lowercase_ext() { + let t = dummy_context().await; let blob = BlobObject::create(&t.ctx, "foo.TXT", b"hello").unwrap(); assert_eq!(blob.as_name(), "$BLOBDIR/foo.txt"); } - #[test] - fn test_as_file_name() { - let t = dummy_context(); + #[async_std::test] + async fn test_as_file_name() { + let t = dummy_context().await; let blob = BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap(); assert_eq!(blob.as_file_name(), "foo.txt"); } - #[test] - fn test_as_rel_path() { - let t = dummy_context(); + #[async_std::test] + async fn test_as_rel_path() { + let t = dummy_context().await; let blob = BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap(); assert_eq!(blob.as_rel_path(), Path::new("foo.txt")); } - #[test] - fn test_suffix() { - let t = dummy_context(); + #[async_std::test] + async fn test_suffix() { + let t = dummy_context().await; let blob = BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap(); assert_eq!(blob.suffix(), Some("txt")); let blob = BlobObject::create(&t.ctx, "bar", b"world").unwrap(); assert_eq!(blob.suffix(), None); } - #[test] - fn test_create_dup() { - let t = dummy_context(); + #[async_std::test] + async fn test_create_dup() { + let t = dummy_context().await; BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap(); let foo_path = t.ctx.get_blobdir().join("foo.txt"); assert!(foo_path.exists()); @@ -546,9 +546,9 @@ mod tests { } } - #[test] - fn test_double_ext_preserved() { - let t = dummy_context(); + #[async_std::test] + async fn test_double_ext_preserved() { + let t = dummy_context().await; BlobObject::create(&t.ctx, "foo.tar.gz", b"hello").unwrap(); let foo_path = t.ctx.get_blobdir().join("foo.tar.gz"); assert!(foo_path.exists()); @@ -566,18 +566,18 @@ mod tests { } } - #[test] - fn test_create_long_names() { - let t = dummy_context(); + #[async_std::test] + async fn test_create_long_names() { + let t = dummy_context().await; let s = "1".repeat(150); let blob = BlobObject::create(&t.ctx, &s, b"data").unwrap(); let blobname = blob.as_name().split('/').last().unwrap(); assert!(blobname.len() < 128); } - #[test] - fn test_create_and_copy() { - let t = dummy_context(); + #[async_std::test] + async fn test_create_and_copy() { + let t = dummy_context().await; let src = t.dir.path().join("src"); fs::write(&src, b"boo").unwrap(); let blob = BlobObject::create_and_copy(&t.ctx, &src).unwrap(); @@ -591,9 +591,9 @@ mod tests { assert!(!whoops.exists()); } - #[test] - fn test_create_from_path() { - let t = dummy_context(); + #[async_std::test] + async fn test_create_from_path() { + let t = dummy_context().await; let src_ext = t.dir.path().join("external"); fs::write(&src_ext, b"boo").unwrap(); @@ -609,9 +609,9 @@ mod tests { let data = fs::read(blob.to_abs_path()).unwrap(); assert_eq!(data, b"boo"); } - #[test] - fn test_create_from_name_long() { - let t = dummy_context(); + #[async_std::test] + async fn test_create_from_name_long() { + let t = dummy_context().await; let src_ext = t.dir.path().join("autocrypt-setup-message-4137848473.html"); fs::write(&src_ext, b"boo").unwrap(); let blob = BlobObject::new_from_path(&t.ctx, &src_ext).unwrap(); diff --git a/src/chat.rs b/src/chat.rs index e81589fcc..af4ef43d4 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -119,7 +119,7 @@ impl ChatId { "UPDATE contacts SET selfavatar_sent=? WHERE id IN(SELECT contact_id FROM chats_contacts WHERE chat_id=?);", - params![timestamp, self], + paramsv![timestamp, self], ) .await?; Ok(()) @@ -134,14 +134,14 @@ impl ChatId { .sql .execute( "UPDATE chats SET blocked=? WHERE id=?;", - params![new_blocked, self], + paramsv![new_blocked, self], ) .await .is_ok() } - pub fn unblock(self, context: &Context) { - self.set_blocked(context, Blocked::Not); + pub async fn unblock(self, context: &Context) { + self.set_blocked(context, Blocked::Not).await; } /// Archives or unarchives a chat. @@ -161,7 +161,7 @@ impl ChatId { .sql .execute( "UPDATE msgs SET state=? WHERE chat_id=? AND state=?;", - params![MessageState::InNoticed, self, MessageState::InFresh], + paramsv![MessageState::InNoticed, self, MessageState::InFresh], ) .await?; } @@ -170,7 +170,7 @@ impl ChatId { .sql .execute( "UPDATE chats SET archived=? WHERE id=?;", - params![visibility, self], + paramsv![visibility, self], ) .await?; @@ -189,7 +189,7 @@ impl ChatId { .sql .execute( "UPDATE chats SET archived=0 WHERE id=? and archived=1", - params![self], + paramsv![self], ) .await?; Ok(()) @@ -209,23 +209,26 @@ impl ChatId { .sql .execute( "DELETE FROM msgs_mdns WHERE msg_id IN (SELECT id FROM msgs WHERE chat_id=?);", - params![self], + paramsv![self], ) .await?; context .sql - .execute("DELETE FROM msgs WHERE chat_id=?;", params![self]) + .execute("DELETE FROM msgs WHERE chat_id=?;", paramsv![self]) .await?; context .sql - .execute("DELETE FROM chats_contacts WHERE chat_id=?;", params![self]) + .execute( + "DELETE FROM chats_contacts WHERE chat_id=?;", + paramsv![self], + ) .await?; context .sql - .execute("DELETE FROM chats WHERE id=?;", params![self]) + .execute("DELETE FROM chats WHERE id=?;", paramsv![self]) .await?; context.call_cb(Event::MsgsChanged { @@ -272,10 +275,10 @@ impl ChatId { async fn get_draft_msg_id(self, context: &Context) -> Option { context .sql - .query_get_value::<_, MsgId>( + .query_get_value::( context, "SELECT id FROM msgs WHERE chat_id=? AND state=?;", - params![self, MessageState::OutDraft], + paramsv![self, MessageState::OutDraft], ) .await } @@ -299,7 +302,7 @@ impl ChatId { async fn maybe_delete_draft(self, context: &Context) -> bool { match self.get_draft_msg_id(context).await { Some(msg_id) => { - Message::delete_from_db(context, msg_id); + Message::delete_from_db(context, msg_id).await; true } None => false, @@ -333,13 +336,13 @@ impl ChatId { .execute( "INSERT INTO msgs (chat_id, from_id, timestamp, type, state, txt, param, hidden) VALUES (?,?,?, ?,?,?,?,?);", - params![ + paramsv![ self, DC_CONTACT_ID_SELF, time(), msg.viewtype, MessageState::OutDraft, - msg.text.as_ref().map(String::as_str).unwrap_or(""), + msg.text.as_ref().cloned().unwrap_or_default(), msg.param.to_string(), 1, ], @@ -353,10 +356,10 @@ impl ChatId { pub async fn get_msg_cnt(self, context: &Context) -> usize { context .sql - .query_get_value::<_, i32>( + .query_get_value::( context, "SELECT COUNT(*) FROM msgs WHERE chat_id=?;", - params![self], + paramsv![self], ) .await .unwrap_or_default() as usize @@ -365,14 +368,14 @@ impl ChatId { pub async fn get_fresh_msg_cnt(self, context: &Context) -> usize { context .sql - .query_get_value::<_, i32>( + .query_get_value::( context, "SELECT COUNT(*) FROM msgs WHERE state=10 AND hidden=0 AND chat_id=?;", - params![self], + paramsv![self], ) .await .unwrap_or_default() as usize @@ -459,7 +462,7 @@ impl Chat { c.blocked, c.locations_send_until, c.muted_until FROM chats c WHERE c.id=?;", - params![chat_id], + paramsv![chat_id], |row| { let c = Chat { id: chat_id, @@ -490,13 +493,13 @@ impl Chat { } Ok(mut chat) => { if chat.id.is_deaddrop() { - chat.name = context.stock_str(StockMessage::DeadDrop).into(); + chat.name = context.stock_str(StockMessage::DeadDrop).await.into(); } else if chat.id.is_archived_link() { - let tempname = context.stock_str(StockMessage::ArchivedChats); + let tempname = context.stock_str(StockMessage::ArchivedChats).await; let cnt = dc_get_archived_cnt(context).await; chat.name = format!("{} ({})", tempname, cnt); } else if chat.id.is_starred() { - chat.name = context.stock_str(StockMessage::StarredMsgs).into(); + chat.name = context.stock_str(StockMessage::StarredMsgs).await.into(); } else { if chat.typ == Chattype::Single { let contacts = get_chat_contacts(context, chat.id).await; @@ -509,9 +512,9 @@ impl Chat { chat.name = chat_name; } if chat.param.exists(Param::Selftalk) { - chat.name = context.stock_str(StockMessage::SavedMessages).into(); + chat.name = context.stock_str(StockMessage::SavedMessages).await.into(); } else if chat.param.exists(Param::Devicetalk) { - chat.name = context.stock_str(StockMessage::DeviceMessages).into(); + chat.name = context.stock_str(StockMessage::DeviceMessages).await.into(); } } Ok(chat) @@ -538,7 +541,7 @@ impl Chat { .sql .execute( "UPDATE chats SET param=? WHERE id=?", - params![self.param.to_string(), self.id], + paramsv![self.param.to_string(), self.id], ) .await?; Ok(()) @@ -563,7 +566,10 @@ impl Chat { // returns either the address or the number of chat members if self.typ == Chattype::Single && self.param.exists(Param::Selftalk) { - return context.stock_str(StockMessage::SelfTalkSubTitle).into(); + return context + .stock_str(StockMessage::SelfTalkSubTitle) + .await + .into(); } if self.typ == Chattype::Single { @@ -575,7 +581,7 @@ impl Chat { FROM chats_contacts cc LEFT JOIN contacts c ON c.id=cc.contact_id WHERE cc.chat_id=?;", - params![self.id], + paramsv![self.id], ) .await .unwrap_or_else(|| "Err".into()); @@ -583,10 +589,12 @@ impl Chat { if self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup { if self.id.is_deaddrop() { - return context.stock_str(StockMessage::DeadDrop).into(); + return context.stock_str(StockMessage::DeadDrop).await.into(); } let cnt = get_chat_contact_cnt(context, self.id).await; - return context.stock_string_repl_int(StockMessage::Member, cnt as i32); + return context + .stock_string_repl_int(StockMessage::Member, cnt as i32) + .await; } "Err".to_string() @@ -606,7 +614,7 @@ impl Chat { async fn get_parent_mime_headers(&self, context: &Context) -> Option<(String, String, String)> { let collect = |row: &rusqlite::Row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)); - let params = params![self.id]; + let params = paramsv![self.id]; let sql = &context.sql; let query = Self::parent_query("rfc724_mid, mime_in_reply_to, mime_references"); @@ -616,7 +624,7 @@ impl Chat { async fn parent_is_encrypted(&self, context: &Context) -> Result { let sql = &context.sql; - let params = params![self.id]; + let params = paramsv![self.id]; let query = Self::parent_query("param"); let packed: Option = sql.query_get_value_result(&query, params).await?; @@ -771,7 +779,7 @@ impl Chat { .query_get_value( context, "SELECT contact_id FROM chats_contacts WHERE chat_id=?;", - params![self.id], + paramsv![self.id], ) .await { @@ -809,7 +817,7 @@ impl Chat { LEFT JOIN contacts c ON cc.contact_id=c.id \ LEFT JOIN acpeerstates ps ON c.addr=ps.addr \ WHERE cc.chat_id=? AND cc.contact_id>9;", - params![self.id], + paramsv![self.id], |row| { let addr: String = row.get(1)?; @@ -897,7 +905,7 @@ impl Chat { "INSERT INTO locations \ (timestamp,from_id,chat_id, latitude,longitude,independent)\ VALUES (?,?,?, ?,?,1);", // 1=DC_CONTACT_ID_SELF - params![ + paramsv![ timestamp, DC_CONTACT_ID_SELF, self.id, @@ -925,7 +933,7 @@ impl Chat { if context.sql.execute( "INSERT INTO msgs (rfc724_mid, chat_id, from_id, to_id, timestamp, type, state, txt, param, hidden, mime_in_reply_to, mime_references, location_id) VALUES (?,?,?,?,?, ?,?,?,?,?, ?,?,?);", - params![ + paramsv![ new_rfc724_mid, self.id, DC_CONTACT_ID_SELF, @@ -933,7 +941,7 @@ impl Chat { timestamp, msg.viewtype, msg.state, - msg.text.as_ref().map_or("", String::as_str), + msg.text.as_ref().cloned().unwrap_or_default(), msg.param.to_string(), msg.hidden, new_in_reply_to, @@ -1093,7 +1101,7 @@ pub async fn create_by_msg_id(context: &Context, msg_id: MsgId) -> Result Result Result< Ok((chat_id, chat_blocked)) => { if chat_blocked != Blocked::Not { // unblock chat (typically move it from the deaddrop to view - chat_id.unblock(context); + chat_id.unblock(context).await; } chat_id } @@ -1132,7 +1140,7 @@ pub async fn create_by_contact_id(context: &Context, contact_id: u32) -> Result< } else { let (chat_id, _) = create_or_lookup_by_contact_id(context, contact_id, Blocked::Not).await?; - Contact::scaleup_origin_by_id(context, contact_id, Origin::CreateChat); + Contact::scaleup_origin_by_id(context, contact_id, Origin::CreateChat).await; chat_id } } @@ -1184,13 +1192,13 @@ async fn update_special_chat_name( stock_id: StockMessage, ) -> Result<(), Error> { if let Ok((chat_id, _)) = lookup_by_contact_id(context, contact_id).await { - let name: String = context.stock_str(stock_id).into(); + let name: String = context.stock_str(stock_id).await.into(); // the `!= name` condition avoids unneeded writes context .sql .execute( "UPDATE chats SET name=? WHERE id=? AND name!=?;", - params![name, chat_id, name], + paramsv![name, chat_id, name], ) .await?; } @@ -1274,7 +1282,7 @@ pub(crate) async fn lookup_by_contact_id( WHERE c.type=100 AND c.id>9 AND j.contact_id=?;", - params![contact_id as i32], + paramsv![contact_id as i32], |row| { Ok(( row.get::<_, ChatId>(0)?, @@ -1390,7 +1398,7 @@ async fn prepare_msg_common( } msg.id = chat - .prepare_msg_raw(context, msg, dc_create_smeared_timestamp(context)) + .prepare_msg_raw(context, msg, dc_create_smeared_timestamp(context).await) .await?; msg.chat_id = chat_id; @@ -1408,7 +1416,7 @@ pub async fn is_contact_in_chat(context: &Context, chat_id: ChatId, contact_id: .sql .exists( "SELECT contact_id FROM chats_contacts WHERE chat_id=? AND contact_id=?;", - params![chat_id, contact_id as i32], + paramsv![chat_id, contact_id as i32], ) .await .unwrap_or_default() @@ -1443,7 +1451,7 @@ pub async fn send_msg( } } msg.param.remove(Param::PrepForwards); - msg.save_param_to_disk(context); + msg.save_param_to_disk(context).await; } return send_msg_inner(context, chat_id, msg).await; } @@ -1469,7 +1477,7 @@ async fn send_msg_inner( chat_id.is_unset() || chat_id == msg.chat_id, "Inconsistent chat ID" ); - message::update_msg_state(context, msg.id, MessageState::OutPending); + message::update_msg_state(context, msg.id, MessageState::OutPending).await; } job::send_msg(context, msg.id).await?; @@ -1551,7 +1559,7 @@ pub async fn get_chat_msgs( AND contacts.blocked=0 AND m.msgrmsg>=? ORDER BY m.timestamp,m.id;", - params![if show_emails == ShowEmails::All { 0 } else { 1 }], + paramsv![if show_emails == ShowEmails::All { 0 } else { 1 }], process_row, process_rows, ) @@ -1568,7 +1576,7 @@ pub async fn get_chat_msgs( AND m.hidden=0 AND ct.blocked=0 ORDER BY m.timestamp,m.id;", - params![], + paramsv![], process_row, process_rows, ) @@ -1582,7 +1590,7 @@ pub async fn get_chat_msgs( WHERE m.chat_id=? AND m.hidden=0 ORDER BY m.timestamp, m.id;", - params![chat_id], + paramsv![chat_id], process_row, process_rows, ) @@ -1602,7 +1610,7 @@ pub async fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<(), .sql .exists( "SELECT id FROM msgs WHERE chat_id=? AND state=?;", - params![chat_id, MessageState::InFresh], + paramsv![chat_id, MessageState::InFresh], ) .await? { @@ -1616,7 +1624,7 @@ pub async fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<(), SET state=13 WHERE chat_id=? AND state=10;", - params![chat_id], + paramsv![chat_id], ) .await?; @@ -1635,7 +1643,7 @@ pub async fn marknoticed_all_chats(context: &Context) -> Result<(), Error> { "SELECT id FROM msgs WHERE state=10;", - params![], + paramsv![], ) .await? { @@ -1648,7 +1656,7 @@ pub async fn marknoticed_all_chats(context: &Context) -> Result<(), Error> { "UPDATE msgs SET state=13 WHERE state=10;", - params![], + paramsv![], ) .await?; @@ -1676,7 +1684,7 @@ pub async fn get_chat_media( WHERE chat_id=? AND (type=? OR type=? OR type=?) ORDER BY timestamp, id;", - params![ + paramsv![ chat_id, msg_type, if msg_type2 != Viewtype::Unknown { @@ -1777,7 +1785,7 @@ pub async fn get_chat_contacts(context: &Context, chat_id: ChatId) -> Vec { ON c.id=cc.contact_id WHERE cc.chat_id=? ORDER BY c.id=1, LOWER(c.name||c.addr), c.id;", - params![chat_id], + paramsv![chat_id], |row| row.get::<_, u32>(0), |ids| ids.collect::, _>>().map_err(Into::into), ) @@ -1792,18 +1800,20 @@ pub async fn create_group_chat( ) -> Result { ensure!(!chat_name.as_ref().is_empty(), "Invalid chat name"); - let draft_txt = context.stock_string_repl_str(StockMessage::NewGroupDraft, &chat_name); + let draft_txt = context + .stock_string_repl_str(StockMessage::NewGroupDraft, &chat_name) + .await; let grpid = dc_create_id(); context.sql.execute( "INSERT INTO chats (type, name, grpid, param, created_timestamp) VALUES(?, ?, ?, \'U=1\', ?);", - params![ + paramsv![ if verified != VerifiedStatus::Unverified { Chattype::VerifiedGroup } else { Chattype::Group }, - chat_name.as_ref(), + chat_name.as_ref().to_string(), grpid, time(), ], @@ -1818,7 +1828,7 @@ pub async fn create_group_chat( if add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF).await { let mut draft_msg = Message::new(Viewtype::Text); draft_msg.set_text(Some(draft_txt)); - chat_id.set_draft_raw(context, &mut draft_msg); + chat_id.set_draft_raw(context, &mut draft_msg).await; } context.call_cb(Event::MsgsChanged { @@ -1841,7 +1851,7 @@ pub(crate) async fn add_to_chat_contacts_table( .sql .execute( "INSERT INTO chats_contacts (chat_id, contact_id) VALUES(?, ?)", - params![chat_id, contact_id as i32], + paramsv![chat_id, contact_id as i32], ) .await .is_ok() @@ -1858,7 +1868,7 @@ pub(crate) async fn remove_from_chat_contacts_table( .sql .execute( "DELETE FROM chats_contacts WHERE chat_id=? AND contact_id=?", - params![chat_id, contact_id as i32], + paramsv![chat_id, contact_id as i32], ) .await .is_ok() @@ -1933,7 +1943,7 @@ pub(crate) async fn add_contact_to_chat_ex( } else { // else continue and send status mail if chat.typ == Chattype::VerifiedGroup - && contact.is_verified(context) != VerifiedStatus::BidirectVerified + && contact.is_verified(context).await != VerifiedStatus::BidirectVerified { error!( context, @@ -1947,12 +1957,16 @@ pub(crate) async fn add_contact_to_chat_ex( } if chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 0 { msg.viewtype = Viewtype::Text; - msg.text = Some(context.stock_system_msg( - StockMessage::MsgAddMember, - contact.get_addr(), - "", - DC_CONTACT_ID_SELF as u32, - )); + msg.text = Some( + context + .stock_system_msg( + StockMessage::MsgAddMember, + contact.get_addr(), + "", + DC_CONTACT_ID_SELF as u32, + ) + .await, + ); msg.param.set_cmd(SystemMessage::MemberAddedToGroup); msg.param.set(Param::Arg, contact.get_addr()); msg.param.set_int(Param::Arg2, from_handshake.into()); @@ -1980,7 +1994,7 @@ async fn real_group_exists(context: &Context, chat_id: ChatId) -> bool { .sql .exists( "SELECT id FROM chats WHERE id=? AND (type=120 OR type=130);", - params![chat_id], + paramsv![chat_id], ) .await .unwrap_or_default() @@ -1998,10 +2012,10 @@ pub(crate) async fn reset_gossiped_timestamp( pub async fn get_gossiped_timestamp(context: &Context, chat_id: ChatId) -> i64 { context .sql - .query_get_value::<_, i64>( + .query_get_value::( context, "SELECT gossiped_timestamp FROM chats WHERE id=?;", - params![chat_id], + paramsv![chat_id], ) .await .unwrap_or_default() @@ -2022,7 +2036,7 @@ pub(crate) async fn set_gossiped_timestamp( .sql .execute( "UPDATE chats SET gossiped_timestamp=? WHERE id=?;", - params![timestamp, chat_id], + paramsv![timestamp, chat_id], ) .await?; @@ -2052,7 +2066,7 @@ pub(crate) async fn shall_attach_selfavatar( FROM chats_contacts cc LEFT JOIN contacts c ON c.id=cc.contact_id WHERE cc.chat_id=? AND cc.contact_id!=?;", - params![chat_id, DC_CONTACT_ID_SELF], + paramsv![chat_id, DC_CONTACT_ID_SELF], |row| Ok(row.get::<_, i64>(0)), |rows| { let mut needs_attach = false; @@ -2124,7 +2138,7 @@ pub async fn set_muted( .sql .execute( "UPDATE chats SET muted_until=? WHERE id=?;", - params![duration, chat_id], + paramsv![duration, chat_id], ) .await .is_ok() @@ -2172,19 +2186,27 @@ pub async fn remove_contact_from_chat( msg.viewtype = Viewtype::Text; if contact.id == DC_CONTACT_ID_SELF { set_group_explicitly_left(context, chat.grpid).await?; - msg.text = Some(context.stock_system_msg( - StockMessage::MsgGroupLeft, - "", - "", - DC_CONTACT_ID_SELF, - )); + msg.text = Some( + context + .stock_system_msg( + StockMessage::MsgGroupLeft, + "", + "", + DC_CONTACT_ID_SELF, + ) + .await, + ); } else { - msg.text = Some(context.stock_system_msg( - StockMessage::MsgDelMember, - contact.get_addr(), - "", - DC_CONTACT_ID_SELF, - )); + msg.text = Some( + context + .stock_system_msg( + StockMessage::MsgDelMember, + contact.get_addr(), + "", + DC_CONTACT_ID_SELF, + ) + .await, + ); } msg.param.set_cmd(SystemMessage::MemberRemovedFromGroup); msg.param.set(Param::Arg, contact.get_addr()); @@ -2216,7 +2238,7 @@ async fn set_group_explicitly_left(context: &Context, grpid: impl AsRef) -> .sql .execute( "INSERT INTO leftgrps (grpid) VALUES(?);", - params![grpid.as_ref()], + paramsv![grpid.as_ref().to_string()], ) .await?; } @@ -2232,7 +2254,7 @@ pub(crate) async fn is_group_explicitly_left( .sql .exists( "SELECT id FROM leftgrps WHERE grpid=?;", - params![grpid.as_ref()], + paramsv![grpid.as_ref()], ) .await .map_err(Into::into) @@ -2266,19 +2288,23 @@ pub async fn set_chat_name( .sql .execute( "UPDATE chats SET name=? WHERE id=?;", - params![new_name.as_ref(), chat_id], + paramsv![new_name.as_ref().to_string(), chat_id], ) .await .is_ok() { if chat.is_promoted() { msg.viewtype = Viewtype::Text; - msg.text = Some(context.stock_system_msg( - StockMessage::MsgGrpName, - &chat.name, - new_name.as_ref(), - DC_CONTACT_ID_SELF, - )); + msg.text = Some( + context + .stock_system_msg( + StockMessage::MsgGrpName, + &chat.name, + new_name.as_ref(), + DC_CONTACT_ID_SELF, + ) + .await, + ); msg.param.set_cmd(SystemMessage::GroupNameChanged); if !chat.name.is_empty() { msg.param.set(Param::Arg, &chat.name); @@ -2332,12 +2358,11 @@ pub async fn set_chat_profile_image( if new_image.as_ref().is_empty() { chat.param.remove(Param::ProfileImage); msg.param.remove(Param::Arg); - msg.text = Some(context.stock_system_msg( - StockMessage::MsgGrpImgDeleted, - "", - "", - DC_CONTACT_ID_SELF, - )); + msg.text = Some( + context + .stock_system_msg(StockMessage::MsgGrpImgDeleted, "", "", DC_CONTACT_ID_SELF) + .await, + ); } else { let image_blob = BlobObject::from_path(context, Path::new(new_image.as_ref())).or_else( |err| match err { @@ -2350,12 +2375,11 @@ pub async fn set_chat_profile_image( image_blob.recode_to_avatar_size(context)?; chat.param.set(Param::ProfileImage, image_blob.as_name()); msg.param.set(Param::Arg, image_blob.as_name()); - msg.text = Some(context.stock_system_msg( - StockMessage::MsgGrpImgChanged, - "", - "", - DC_CONTACT_ID_SELF, - )); + msg.text = Some( + context + .stock_system_msg(StockMessage::MsgGrpImgChanged, "", "", DC_CONTACT_ID_SELF) + .await, + ); } chat.update_param(context).await?; if chat.is_promoted() { @@ -2387,7 +2411,7 @@ pub async fn forward_msgs( chat_id.unarchive(context).await?; if let Ok(mut chat) = Chat::load_from_db(context, chat_id).await { ensure!(chat.can_send(), "cannot send to {}", chat_id); - curr_timestamp = dc_create_smeared_timestamps(context, msg_ids.len()); + curr_timestamp = dc_create_smeared_timestamps(context, msg_ids.len()).await; let ids = context .sql .query_map( @@ -2395,7 +2419,7 @@ pub async fn forward_msgs( "SELECT id FROM msgs WHERE id IN({}) ORDER BY timestamp,id", msg_ids.iter().map(|_| "?").join(",") ), - msg_ids, + msg_ids.iter().map(|v| v as &dyn crate::ToSql).collect(), |row| row.get::<_, MsgId>(0), |ids| ids.collect::, _>>().map_err(Into::into), ) @@ -2436,7 +2460,7 @@ pub async fn forward_msgs( .set(Param::PrepForwards, new_msg_id.to_u32().to_string()); } - msg.save_param_to_disk(context); + msg.save_param_to_disk(context).await; msg.param = save_param; } else { msg.state = MessageState::OutPending; @@ -2461,10 +2485,10 @@ pub async fn forward_msgs( pub(crate) async fn get_chat_contact_cnt(context: &Context, chat_id: ChatId) -> usize { context .sql - .query_get_value::<_, isize>( + .query_get_value::( context, "SELECT COUNT(*) FROM chats_contacts WHERE chat_id=?;", - params![chat_id], + paramsv![chat_id], ) .await .unwrap_or_default() as usize @@ -2475,10 +2499,10 @@ pub(crate) async fn get_chat_cnt(context: &Context) -> usize { /* no database, no chats - this is no error (needed eg. for information) */ context .sql - .query_get_value::<_, isize>( + .query_get_value::( context, "SELECT COUNT(*) FROM chats WHERE id>9 AND blocked=0;", - params![], + paramsv![], ) .await .unwrap_or_default() as usize @@ -2495,7 +2519,7 @@ pub(crate) async fn get_chat_id_by_grpid( .sql .query_row( "SELECT id, blocked, type FROM chats WHERE grpid=?;", - params![grpid.as_ref()], + paramsv![grpid.as_ref()], |row| { let chat_id = row.get::<_, ChatId>(0)?; @@ -2535,21 +2559,21 @@ pub async fn add_device_msg( .0; let rfc724_mid = dc_create_outgoing_rfc724_mid(None, "@device"); - msg.try_calc_and_set_dimensions(context).ok(); + msg.try_calc_and_set_dimensions(context).await.ok(); prepare_msg_blob(context, msg)?; chat_id.unarchive(context).await?; context.sql.execute( "INSERT INTO msgs (chat_id,from_id,to_id, timestamp,type,state, txt,param,rfc724_mid) \ VALUES (?,?,?, ?,?,?, ?,?,?);", - params![ + paramsv![ chat_id, DC_CONTACT_ID_DEVICE, DC_CONTACT_ID_SELF, - dc_create_smeared_timestamp(context), + dc_create_smeared_timestamp(context).await, msg.viewtype, MessageState::InFresh, - msg.text.as_ref().map_or("", String::as_str), + msg.text.as_ref().cloned().unwrap_or_default(), msg.param.to_string(), rfc724_mid, ], @@ -2567,7 +2591,7 @@ pub async fn add_device_msg( .sql .execute( "INSERT INTO devmsglabels (label) VALUES (?);", - params![label], + paramsv![label.to_string()], ) .await?; } @@ -2585,7 +2609,7 @@ pub async fn was_device_msg_ever_added(context: &Context, label: &str) -> Result .sql .query_row( "SELECT label FROM devmsglabels WHERE label=?", - params![label], + paramsv![label], |_| Ok(()), ) .await @@ -2606,12 +2630,12 @@ pub(crate) async fn delete_and_reset_all_device_msgs(context: &Context) -> Resul .sql .execute( "DELETE FROM msgs WHERE from_id=?;", - params![DC_CONTACT_ID_DEVICE], + paramsv![DC_CONTACT_ID_DEVICE], ) .await?; context .sql - .execute("DELETE FROM devmsglabels;", params![]) + .execute("DELETE FROM devmsglabels;", paramsv![]) .await?; Ok(()) } @@ -2624,14 +2648,14 @@ pub(crate) async fn add_info_msg(context: &Context, chat_id: ChatId, text: impl if context.sql.execute( "INSERT INTO msgs (chat_id,from_id,to_id, timestamp,type,state, txt,rfc724_mid) VALUES (?,?,?, ?,?,?, ?,?);", - params![ + paramsv![ chat_id, DC_CONTACT_ID_INFO, DC_CONTACT_ID_INFO, - dc_create_smeared_timestamp(context), + dc_create_smeared_timestamp(context).await, Viewtype::Text, MessageState::InNoticed, - text.as_ref(), + text.as_ref().to_string(), rfc724_mid, ] ).await.is_err() { @@ -2658,7 +2682,7 @@ mod tests { #[async_std::test] async fn test_chat_info() { - let t = dummy_context(); + let t = dummy_context().await; let bob = Contact::create(&t.ctx, "bob", "bob@example.com") .await .unwrap(); @@ -2693,7 +2717,7 @@ mod tests { #[async_std::test] async fn test_get_draft_no_draft() { - let t = dummy_context(); + let t = dummy_context().await; let chat_id = create_by_contact_id(&t.ctx, DC_CONTACT_ID_SELF) .await .unwrap(); @@ -2703,7 +2727,7 @@ mod tests { #[async_std::test] async fn test_get_draft_special_chat_id() { - let t = dummy_context(); + let t = dummy_context().await; let draft = ChatId::new(DC_CHAT_ID_LAST_SPECIAL) .get_draft(&t.ctx) .await @@ -2715,20 +2739,20 @@ mod tests { async fn test_get_draft_no_chat() { // This is a weird case, maybe this should be an error but we // do not get this info from the database currently. - let t = dummy_context(); + let t = dummy_context().await; let draft = ChatId::new(42).get_draft(&t.ctx).await.unwrap(); assert!(draft.is_none()); } #[async_std::test] async fn test_get_draft() { - let t = dummy_context(); + let t = dummy_context().await; let chat_id = create_by_contact_id(&t.ctx, DC_CONTACT_ID_SELF) .await .unwrap(); let mut msg = Message::new(Viewtype::Text); msg.set_text(Some("hello".to_string())); - chat_id.set_draft(&t.ctx, Some(&mut msg)); + chat_id.set_draft(&t.ctx, Some(&mut msg)).await; let draft = chat_id.get_draft(&t.ctx).await.unwrap().unwrap(); let msg_text = msg.get_text(); let draft_text = draft.get_text(); @@ -2738,7 +2762,7 @@ mod tests { #[async_std::test] async fn test_add_contact_to_chat_ex_add_self() { // Adding self to a contact should succeed, even though it's pointless. - let t = test_context(Some(Box::new(logging_cb))); + let t = test_context(Some(Box::new(logging_cb))).await; let chat_id = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo") .await .unwrap(); @@ -2750,7 +2774,7 @@ mod tests { #[async_std::test] async fn test_self_talk() { - let t = dummy_context(); + let t = dummy_context().await; let chat_id = create_by_contact_id(&t.ctx, DC_CONTACT_ID_SELF) .await .unwrap(); @@ -2762,13 +2786,16 @@ mod tests { assert!(chat.visibility == ChatVisibility::Normal); assert!(!chat.is_device_talk()); assert!(chat.can_send()); - assert_eq!(chat.name, t.ctx.stock_str(StockMessage::SavedMessages)); + assert_eq!( + chat.name, + t.ctx.stock_str(StockMessage::SavedMessages).await + ); assert!(chat.get_profile_image(&t.ctx).await.is_some()); } #[async_std::test] async fn test_deaddrop_chat() { - let t = dummy_context(); + let t = dummy_context().await; let chat = Chat::load_from_db(&t.ctx, ChatId::new(DC_CHAT_ID_DEADDROP)) .await .unwrap(); @@ -2778,12 +2805,12 @@ mod tests { assert!(chat.visibility == ChatVisibility::Normal); assert!(!chat.is_device_talk()); assert!(!chat.can_send()); - assert_eq!(chat.name, t.ctx.stock_str(StockMessage::DeadDrop)); + assert_eq!(chat.name, t.ctx.stock_str(StockMessage::DeadDrop).await); } #[async_std::test] async fn test_add_device_msg_unlabelled() { - let t = test_context(Some(Box::new(logging_cb))); + let t = test_context(Some(Box::new(logging_cb))).await; // add two device-messages let mut msg1 = Message::new(Viewtype::Text); @@ -2818,7 +2845,7 @@ mod tests { #[async_std::test] async fn test_add_device_msg_labelled() { - let t = test_context(Some(Box::new(logging_cb))); + let t = test_context(Some(Box::new(logging_cb))).await; // add two device-messages with the same label (second attempt is not added) let mut msg1 = Message::new(Viewtype::Text); @@ -2855,7 +2882,10 @@ mod tests { assert!(chat.is_device_talk()); assert!(!chat.is_self_talk()); assert!(!chat.can_send()); - assert_eq!(chat.name, t.ctx.stock_str(StockMessage::DeviceMessages)); + assert_eq!( + chat.name, + t.ctx.stock_str(StockMessage::DeviceMessages).await + ); assert!(chat.get_profile_image(&t.ctx).await.is_some()); // delete device message, make sure it is not added again @@ -2869,7 +2899,7 @@ mod tests { #[async_std::test] async fn test_add_device_msg_label_only() { - let t = test_context(Some(Box::new(logging_cb))); + let t = test_context(Some(Box::new(logging_cb))).await; let res = add_device_msg(&t.ctx, Some(""), None).await; assert!(res.is_err()); let res = add_device_msg(&t.ctx, Some("some-label"), None).await; @@ -2889,7 +2919,7 @@ mod tests { #[async_std::test] async fn test_was_device_msg_ever_added() { - let t = test_context(Some(Box::new(logging_cb))); + let t = test_context(Some(Box::new(logging_cb))).await; add_device_msg(&t.ctx, Some("some-label"), None).await.ok(); assert!(was_device_msg_ever_added(&t.ctx, "some-label") .await @@ -2913,7 +2943,7 @@ mod tests { #[async_std::test] async fn test_delete_device_chat() { - let t = test_context(Some(Box::new(logging_cb))); + let t = test_context(Some(Box::new(logging_cb))).await; let mut msg = Message::new(Viewtype::Text); msg.text = Some("message text".to_string()); @@ -2933,8 +2963,8 @@ mod tests { #[async_std::test] async fn test_device_chat_cannot_sent() { - let t = test_context(Some(Box::new(logging_cb))); - t.ctx.update_device_chats().unwrap(); + let t = test_context(Some(Box::new(logging_cb))).await; + t.ctx.update_device_chats().await.unwrap(); let (device_chat_id, _) = create_or_lookup_by_contact_id(&t.ctx, DC_CONTACT_ID_DEVICE, Blocked::Not) .await @@ -2953,7 +2983,7 @@ mod tests { #[async_std::test] async fn test_delete_and_reset_all_device_msgs() { - let t = test_context(Some(Box::new(logging_cb))); + let t = test_context(Some(Box::new(logging_cb))).await; let mut msg = Message::new(Viewtype::Text); msg.text = Some("message text".to_string()); let msg_id1 = add_device_msg(&t.ctx, Some("some-label"), Some(&mut msg)) @@ -2990,7 +3020,7 @@ mod tests { #[async_std::test] async fn test_archive() { // create two chats - let t = dummy_context(); + let t = dummy_context().await; let mut msg = Message::new(Viewtype::Text); msg.text = Some("foo".to_string()); let msg_id = add_device_msg(&t.ctx, None, Some(&mut msg)).await.unwrap(); @@ -3104,7 +3134,7 @@ mod tests { #[async_std::test] async fn test_pinned() { - let t = dummy_context(); + let t = dummy_context().await; // create 3 chats, wait 1 second in between to get a reliable order (we order by time) let mut msg = Message::new(Viewtype::Text); @@ -3163,7 +3193,7 @@ mod tests { #[async_std::test] async fn test_set_chat_name() { - let t = dummy_context(); + let t = dummy_context().await; let chat_id = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo") .await .unwrap(); @@ -3187,7 +3217,7 @@ mod tests { #[async_std::test] async fn test_create_same_chat_twice() { - let context = dummy_context(); + let context = dummy_context().await; let contact1 = Contact::create(&context.ctx, "bob", "bob@mail.de") .await .unwrap(); @@ -3206,7 +3236,7 @@ mod tests { #[async_std::test] async fn test_shall_attach_selfavatar() { - let t = dummy_context(); + let t = dummy_context().await; let chat_id = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo") .await .unwrap(); @@ -3230,7 +3260,7 @@ mod tests { #[async_std::test] async fn test_set_mute_duration() { - let t = dummy_context(); + let t = dummy_context().await; let chat_id = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo") .await .unwrap(); diff --git a/src/chatlist.rs b/src/chatlist.rs index fa8544654..3fe730181 100644 --- a/src/chatlist.rs +++ b/src/chatlist.rs @@ -136,7 +136,7 @@ impl Chatlist { AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?2) GROUP BY c.id ORDER BY c.archived=?3 DESC, IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;", - params![MessageState::OutDraft, query_contact_id as i32, ChatVisibility::Pinned], + paramsv![MessageState::OutDraft, query_contact_id as i32, ChatVisibility::Pinned], process_row, process_rows, ).await? @@ -159,7 +159,7 @@ impl Chatlist { AND c.archived=1 GROUP BY c.id ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;", - params![MessageState::OutDraft], + paramsv![MessageState::OutDraft], process_row, process_rows, ) @@ -192,7 +192,7 @@ impl Chatlist { AND c.name LIKE ? GROUP BY c.id ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;", - params![MessageState::OutDraft, str_like_cmd], + paramsv![MessageState::OutDraft, str_like_cmd], process_row, process_rows, ) @@ -221,7 +221,7 @@ impl Chatlist { AND NOT c.archived=?2 GROUP BY c.id ORDER BY c.id=?3 DESC, c.archived=?4 DESC, IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;", - params![MessageState::OutDraft, ChatVisibility::Archived, sort_id_up, ChatVisibility::Pinned], + paramsv![MessageState::OutDraft, ChatVisibility::Archived, sort_id_up, ChatVisibility::Pinned], process_row, process_rows, ).await?; @@ -333,9 +333,15 @@ impl Chatlist { ret.text2 = None; } else if lastmsg.is_none() || lastmsg.as_ref().unwrap().from_id == DC_CONTACT_ID_UNDEFINED { - ret.text2 = Some(context.stock_str(StockMessage::NoMessages).to_string()); + ret.text2 = Some( + context + .stock_str(StockMessage::NoMessages) + .await + .to_string(), + ); } else { - ret.fill(&mut lastmsg.unwrap(), chat, lastcontact.as_ref(), context); + ret.fill(&mut lastmsg.unwrap(), chat, lastcontact.as_ref(), context) + .await; } ret @@ -353,7 +359,7 @@ pub async fn dc_get_archived_cnt(context: &Context) -> u32 { .query_get_value( context, "SELECT COUNT(*) FROM chats WHERE blocked=0 AND archived=1;", - params![], + paramsv![], ) .await .unwrap_or_default() @@ -376,7 +382,7 @@ async fn get_last_deaddrop_fresh_msg(context: &Context) -> Option { " AND c.blocked=2", " ORDER BY m.timestamp DESC, m.id DESC;" ), - params![], + paramsv![], ) .await } @@ -389,7 +395,7 @@ mod tests { #[async_std::test] async fn test_try_load() { - let t = dummy_context(); + let t = dummy_context().await; let chat_id1 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "a chat") .await .unwrap(); @@ -410,7 +416,7 @@ mod tests { // drafts are sorted to the top let mut msg = Message::new(Viewtype::Text); msg.set_text(Some("hello".to_string())); - chat_id2.set_draft(&t.ctx, Some(&mut msg)); + chat_id2.set_draft(&t.ctx, Some(&mut msg)).await; let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); assert_eq!(chats.get_chat_id(0), chat_id2); @@ -453,8 +459,8 @@ mod tests { #[async_std::test] async fn test_search_special_chat_names() { - let t = dummy_context(); - t.ctx.update_device_chats().unwrap(); + let t = dummy_context().await; + t.ctx.update_device_chats().await.unwrap(); let chats = Chatlist::try_load(&t.ctx, 0, Some("t-1234-s"), None) .await @@ -467,6 +473,7 @@ mod tests { t.ctx .set_stock_translation(StockMessage::SavedMessages, "test-1234-save".to_string()) + .await .unwrap(); let chats = Chatlist::try_load(&t.ctx, 0, Some("t-1234-s"), None) .await @@ -475,6 +482,7 @@ mod tests { t.ctx .set_stock_translation(StockMessage::DeviceMessages, "test-5678-babbel".to_string()) + .await .unwrap(); let chats = Chatlist::try_load(&t.ctx, 0, Some("t-5678-b"), None) .await @@ -484,14 +492,14 @@ mod tests { #[async_std::test] async fn test_get_summary_unwrap() { - let t = dummy_context(); + let t = dummy_context().await; let chat_id1 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "a chat") .await .unwrap(); let mut msg = Message::new(Viewtype::Text); msg.set_text(Some("foo:\nbar \r\n test".to_string())); - chat_id1.set_draft(&t.ctx, Some(&mut msg)); + chat_id1.set_draft(&t.ctx, Some(&mut msg)).await; let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); let summary = chats.get_summary(&t.ctx, 0, None).await; diff --git a/src/config.rs b/src/config.rs index 62c02e510..187d26735 100644 --- a/src/config.rs +++ b/src/config.rs @@ -10,7 +10,6 @@ use crate::dc_tools::*; use crate::job::*; use crate::mimefactory::RECOMMENDED_FILE_SIZE; use crate::stock::StockMessage; -use rusqlite::NO_PARAMS; /// The available configuration keys. #[derive( @@ -113,7 +112,7 @@ impl Context { // Default values match key { - Config::Selfstatus => Some(self.stock_str(StockMessage::StatusLine).into_owned()), + Config::Selfstatus => Some(self.stock_str(StockMessage::StatusLine).await.into_owned()), _ => key.get_str("default").map(|s| s.to_string()), } } @@ -135,7 +134,7 @@ impl Context { match key { Config::Selfavatar => { self.sql - .execute("UPDATE contacts SET selfavatar_sent=0;", NO_PARAMS) + .execute("UPDATE contacts SET selfavatar_sent=0;", paramsv![]) .await?; self.sql .set_raw_config_bool(self, "attach_selfavatar", true) @@ -167,7 +166,7 @@ impl Context { ret } Config::Selfstatus => { - let def = self.stock_str(StockMessage::StatusLine); + let def = self.stock_str(StockMessage::StatusLine).await; let val = if value.is_none() || value.unwrap() == def { None } else { @@ -224,7 +223,7 @@ mod tests { #[async_std::test] async fn test_selfavatar_outside_blobdir() { - let t = dummy_context(); + let t = dummy_context().await; let avatar_src = t.dir.path().join("avatar.jpg"); let avatar_bytes = include_bytes!("../test-data/image/avatar1000x1000.jpg"); File::create(&avatar_src) @@ -253,7 +252,7 @@ mod tests { #[async_std::test] async fn test_selfavatar_in_blobdir() { - let t = dummy_context(); + let t = dummy_context().await; let avatar_src = t.ctx.get_blobdir().join("avatar.png"); let avatar_bytes = include_bytes!("../test-data/image/avatar900x900.png"); File::create(&avatar_src) diff --git a/src/configure/mod.rs b/src/configure/mod.rs index d069132d2..b73aff7f5 100644 --- a/src/configure/mod.rs +++ b/src/configure/mod.rs @@ -33,7 +33,7 @@ macro_rules! progress { impl Context { /// Starts a configuration job. pub async fn configure(&self) { - if self.has_ongoing() { + if self.has_ongoing().await { warn!(self, "There is already another ongoing process running.",); return; } @@ -57,7 +57,7 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status { progress!(context, 0); return job::Status::Finished(Err(format_err!("Database not opened"))); } - if !context.alloc_ongoing() { + if !context.alloc_ongoing().await { progress!(context, 0); return job::Status::Finished(Err(format_err!("Cannot allocated ongoing process"))); } @@ -87,7 +87,7 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status { const STEP_13_AFTER_AUTOCONFIG: u8 = 13; let mut step_counter: u8 = 0; - while !context.shall_stop_ongoing() { + while !context.shall_stop_ongoing().await { step_counter += 1; let success = match step_counter { @@ -108,6 +108,7 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status { progress!(context, 10); if let Some(oauth2_addr) = dc_get_oauth2_addr(context, ¶m.addr, ¶m.mail_pw) + .await .and_then(|e| e.parse().ok()) { info!(context, "Authorized address is {}", oauth2_addr); @@ -444,7 +445,7 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status { } } - context.free_ongoing(); + context.free_ongoing().await; progress!(context, if success { 1000 } else { 0 }); job::Status::Finished(Ok(())) } @@ -574,7 +575,7 @@ async fn try_imap_one_param(context: &Context, param: &LoginParam) -> Option Option { - if context.shall_stop_ongoing() { + if context.shall_stop_ongoing().await { Some(false) } else { warn!(context, "could not connect: {}", err); @@ -642,7 +643,7 @@ mod tests { #[async_std::test] async fn test_no_panic_on_bad_credentials() { - let t = dummy_context(); + let t = dummy_context().await; t.ctx .set_config(Config::Addr, Some("probably@unexistant.addr")) .await @@ -654,9 +655,9 @@ mod tests { job_configure_imap(&t.ctx).await; } - #[test] - fn test_get_offline_autoconfig() { - let context = dummy_context().ctx; + #[async_std::test] + async fn test_get_offline_autoconfig() { + let context = dummy_context().await.ctx; let mut params = LoginParam::new(); params.addr = "someone123@example.org".to_string(); diff --git a/src/contact.rs b/src/contact.rs index 29a9bab9a..2453c1f91 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -170,7 +170,7 @@ impl Contact { "SELECT c.name, c.addr, c.origin, c.blocked, c.authname, c.param FROM contacts c WHERE c.id=?;", - params![contact_id as i32], + paramsv![contact_id as i32], |row| { let contact = Self { id: contact_id, @@ -186,13 +186,16 @@ impl Contact { ) .await?; if contact_id == DC_CONTACT_ID_SELF { - res.name = context.stock_str(StockMessage::SelfMsg).to_string(); + res.name = context.stock_str(StockMessage::SelfMsg).await.to_string(); res.addr = context .get_config(Config::ConfiguredAddr) .await .unwrap_or_default(); } else if contact_id == DC_CONTACT_ID_DEVICE { - res.name = context.stock_str(StockMessage::DeviceMessages).to_string(); + res.name = context + .stock_str(StockMessage::DeviceMessages) + .await + .to_string(); res.addr = DC_CONTACT_ID_DEVICE_ADDR.to_string(); } Ok(res) @@ -251,7 +254,7 @@ impl Contact { }, )); if blocked { - Contact::unblock(context, contact_id); + Contact::unblock(context, contact_id).await; } Ok(contact_id) @@ -266,7 +269,7 @@ impl Contact { .sql .execute( "UPDATE msgs SET state=? WHERE from_id=? AND state=?;", - params![MessageState::InNoticed, id as i32, MessageState::InFresh], + paramsv![MessageState::InNoticed, id as i32, MessageState::InFresh], ) .await .is_ok() @@ -301,7 +304,7 @@ impl Contact { context.sql.query_get_value( context, "SELECT id FROM contacts WHERE addr=?1 COLLATE NOCASE AND id>?2 AND origin>=?3 AND blocked=0;", - params![ + paramsv![ addr_normalized, DC_CONTACT_ID_LAST_SPECIAL as i32, DC_ORIGIN_MIN_CONTACT_LIST, @@ -348,13 +351,13 @@ impl Contact { ); ensure!(origin != Origin::Unknown, "Missing valid origin"); - let addr = addr_normalize(addr.as_ref()); + let addr = addr_normalize(addr.as_ref()).to_string(); let addr_self = context .get_config(Config::ConfiguredAddr) .await .unwrap_or_default(); - if addr_cmp(addr, addr_self) { + if addr_cmp(&addr, addr_self) { return Ok((DC_CONTACT_ID_SELF, sth_modified)); } @@ -379,7 +382,7 @@ impl Contact { if let Ok((id, row_name, row_addr, row_origin, row_authname)) = context.sql.query_row( "SELECT id, name, addr, origin, authname FROM contacts WHERE addr=? COLLATE NOCASE;", - params![addr], + paramsv![addr.to_string()], |row| { let row_id = row.get(0)?; let row_name: String = row.get(1)?; @@ -416,30 +419,30 @@ impl Contact { if update_name || update_authname || update_addr || origin > row_origin { let new_name = if update_name { if !name.as_ref().is_empty() { - name.as_ref() + name.as_ref().to_string() } else { - &row_authname + row_authname.clone() } } else { - &row_name + row_name }; context .sql .execute( "UPDATE contacts SET name=?, addr=?, origin=?, authname=? WHERE id=?;", - params![ + paramsv![ new_name, - if update_addr { addr } else { &row_addr }, + if update_addr { addr.to_string() } else { row_addr }, if origin > row_origin { origin } else { row_origin }, if update_authname { - name.as_ref() + name.as_ref().to_string() } else { - &row_authname + row_authname }, row_id ], @@ -452,7 +455,7 @@ impl Contact { // This is one of the few duplicated data, however, getting the chat list is easier this way. context.sql.execute( "UPDATE chats SET name=? WHERE type=? AND id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?);", - params![new_name, Chattype::Single, row_id] + paramsv![new_name, Chattype::Single, row_id] ).await.ok(); } sth_modified = Modifier::Modified; @@ -466,11 +469,11 @@ impl Contact { .sql .execute( "INSERT INTO contacts (name, addr, origin, authname) VALUES(?, ?, ?, ?);", - params![ - name.as_ref(), + paramsv![ + name.as_ref().to_string(), addr, origin, - if update_authname { name.as_ref() } else { "" } + if update_authname { name.as_ref().to_string() } else { "".to_string() } ], ) .await @@ -478,10 +481,10 @@ impl Contact { { row_id = context .sql - .get_rowid(context, "contacts", "addr", addr) + .get_rowid(context, "contacts", "addr", &addr) .await?; sth_modified = Modifier::Created; - info!(context, "added contact id={} addr={}", row_id, addr); + info!(context, "added contact id={} addr={}", row_id, &addr); } else { error!(context, "Cannot add contact."); } @@ -577,13 +580,13 @@ impl Contact { AND (c.name LIKE ?4 OR c.addr LIKE ?5) \ AND (1=?6 OR LENGTH(ps.verified_key_fingerprint)!=0) \ ORDER BY LOWER(c.name||c.addr),c.id;", - params![ + paramsv![ self_addr, DC_CONTACT_ID_LAST_SPECIAL as i32, Origin::IncomingReplyTo, - &s3str_like_cmd, - &s3str_like_cmd, - if flag_verified_only { 0 } else { 1 }, + s3str_like_cmd, + s3str_like_cmd, + if flag_verified_only { 0i32 } else { 1i32 }, ], |row| row.get::<_, i32>(0), |ids| { @@ -604,7 +607,7 @@ impl Contact { if let Some(query) = query { if self_addr.contains(query.as_ref()) || self_name.contains(query.as_ref()) - || self_name2.contains(query.as_ref()) + || self_name2.await.contains(query.as_ref()) { add_self = true; } @@ -616,7 +619,7 @@ impl Contact { context.sql.query_map( "SELECT id FROM contacts WHERE addr!=?1 AND id>?2 AND origin>=?3 AND blocked=0 ORDER BY LOWER(name||addr),id;", - params![self_addr, DC_CONTACT_ID_LAST_SPECIAL as i32, 0x100], + paramsv![self_addr, DC_CONTACT_ID_LAST_SPECIAL as i32, 0x100], |row| row.get::<_, i32>(0), |ids| { for id in ids { @@ -637,10 +640,10 @@ impl Contact { pub async fn get_blocked_cnt(context: &Context) -> usize { context .sql - .query_get_value::<_, isize>( + .query_get_value::( context, "SELECT COUNT(*) FROM contacts WHERE id>? AND blocked!=0", - params![DC_CONTACT_ID_LAST_SPECIAL as i32], + paramsv![DC_CONTACT_ID_LAST_SPECIAL as i32], ) .await .unwrap_or_default() as usize @@ -652,7 +655,7 @@ impl Contact { .sql .query_map( "SELECT id FROM contacts WHERE id>? AND blocked!=0 ORDER BY LOWER(name||addr),id;", - params![DC_CONTACT_ID_LAST_SPECIAL as i32], + paramsv![DC_CONTACT_ID_LAST_SPECIAL as i32], |row| row.get::<_, u32>(0), |ids| { ids.collect::, _>>() @@ -672,7 +675,7 @@ impl Contact { let mut ret = String::new(); if let Ok(contact) = Contact::load_from_db(context, contact_id).await { - let peerstate = Peerstate::from_addr(context, &context.sql, &contact.addr); + let peerstate = Peerstate::from_addr(context, &context.sql, &contact.addr).await; let loginparam = LoginParam::from_database(context, "configured_").await; let mut self_key = Key::from_self_public(context, &loginparam.addr, &context.sql).await; @@ -684,18 +687,19 @@ impl Contact { .is_some() { let peerstate = peerstate.as_ref().unwrap(); - let p = - context.stock_str(if peerstate.prefer_encrypt == EncryptPreference::Mutual { + let p = context + .stock_str(if peerstate.prefer_encrypt == EncryptPreference::Mutual { StockMessage::E2ePreferred } else { StockMessage::E2eAvailable - }); + }) + .await; ret += &p; if self_key.is_none() { e2ee::ensure_secret_key_exists(context).await?; self_key = Key::from_self_public(context, &loginparam.addr, &context.sql).await; } - let p = context.stock_str(StockMessage::FingerPrints); + let p = context.stock_str(StockMessage::FingerPrints).await; ret += &format!(" {}:", p); let fingerprint_self = self_key @@ -729,9 +733,9 @@ impl Contact { } else if 0 == loginparam.server_flags & DC_LP_IMAP_SOCKET_PLAIN as i32 && 0 == loginparam.server_flags & DC_LP_SMTP_SOCKET_PLAIN as i32 { - ret += &context.stock_str(StockMessage::EncrTransp); + ret += &context.stock_str(StockMessage::EncrTransp).await; } else { - ret += &context.stock_str(StockMessage::EncrNone); + ret += &context.stock_str(StockMessage::EncrNone).await; } } @@ -753,7 +757,7 @@ impl Contact { .query_get_value( context, "SELECT COUNT(*) FROM chats_contacts WHERE contact_id=?;", - params![contact_id as i32], + paramsv![contact_id as i32], ) .await .unwrap_or_default(); @@ -764,7 +768,7 @@ impl Contact { .query_get_value( context, "SELECT COUNT(*) FROM msgs WHERE from_id=? OR to_id=?;", - params![contact_id as i32, contact_id as i32], + paramsv![contact_id as i32, contact_id as i32], ) .await .unwrap_or_default() @@ -777,7 +781,7 @@ impl Contact { .sql .execute( "DELETE FROM contacts WHERE id=?;", - params![contact_id as i32], + paramsv![contact_id as i32], ) .await { @@ -815,7 +819,7 @@ impl Contact { .sql .execute( "UPDATE contacts SET param=? WHERE id=?", - params![self.param.to_string(), self.id as i32], + paramsv![self.param.to_string(), self.id as i32], ) .await?; Ok(()) @@ -913,17 +917,17 @@ impl Contact { /// /// The UI may draw a checkbox or something like that beside verified contacts. /// - pub fn is_verified(&self, context: &Context) -> VerifiedStatus { - self.is_verified_ex(context, None) + pub async fn is_verified(&self, context: &Context) -> VerifiedStatus { + self.is_verified_ex(context, None).await } /// Same as `Contact::is_verified` but allows speeding up things /// by adding the peerstate belonging to the contact. /// If you do not have the peerstate available, it is loaded automatically. - pub fn is_verified_ex( + pub async fn is_verified_ex( &self, context: &Context, - peerstate: Option<&Peerstate>, + peerstate: Option<&Peerstate<'_>>, ) -> VerifiedStatus { // We're always sort of secured-verified as we could verify the key on this device any time with the key // on this device @@ -937,7 +941,7 @@ impl Contact { } } - let peerstate = Peerstate::from_addr(context, &context.sql, &self.addr); + let peerstate = Peerstate::from_addr(context, &context.sql, &self.addr).await; if let Some(ps) = peerstate { if ps.verified_key.is_some() { return VerifiedStatus::BidirectVerified; @@ -975,10 +979,10 @@ impl Contact { context .sql - .query_get_value::<_, isize>( + .query_get_value::( context, "SELECT COUNT(*) FROM contacts WHERE id>?;", - params![DC_CONTACT_ID_LAST_SPECIAL as i32], + paramsv![DC_CONTACT_ID_LAST_SPECIAL as i32], ) .await .unwrap_or_default() as usize @@ -993,7 +997,7 @@ impl Contact { .sql .exists( "SELECT id FROM contacts WHERE id=?;", - params![contact_id as i32], + paramsv![contact_id as i32], ) .await .unwrap_or_default() @@ -1004,7 +1008,7 @@ impl Contact { .sql .execute( "UPDATE contacts SET origin=? WHERE id=? AND origin Result<()> { - let t = test_context(None); + let t = test_context(None).await; assert!(t.ctx.is_self_addr("me@me.org").await.is_err()); let addr = configure_alice_keypair(&t.ctx).await; @@ -1268,10 +1280,10 @@ mod tests { Ok(()) } - #[test] - fn test_add_or_lookup() { + #[async_std::test] + async fn test_add_or_lookup() { // add some contacts, this also tests add_address_book() - let t = dummy_context(); + let t = dummy_context().await; let book = concat!( " Name one \n one@eins.org \n", "Name two\ntwo@deux.net\n", @@ -1279,15 +1291,16 @@ mod tests { "\nthree@drei.sam\n", "Name two\ntwo@deux.net\n" // should not be added again ); - assert_eq!(Contact::add_address_book(&t.ctx, book).unwrap(), 3); + assert_eq!(Contact::add_address_book(&t.ctx, book).await.unwrap(), 3); // check first added contact, this does not modify because of lower origin let (contact_id, sth_modified) = Contact::add_or_lookup(&t.ctx, "bla foo", "one@eins.org", Origin::IncomingUnknownTo) + .await .unwrap(); assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL); assert_eq!(sth_modified, Modifier::None); - let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap(); + let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap(); assert_eq!(contact.get_id(), contact_id); assert_eq!(contact.get_name(), "Name one"); assert_eq!(contact.get_display_name(), "Name one"); @@ -1301,10 +1314,11 @@ mod tests { " one@eins.org ", Origin::ManuallyCreated, ) + .await .unwrap(); assert_eq!(contact_id, contact_id_test); assert_eq!(sth_modified, Modifier::Modified); - let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap(); + let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap(); assert_eq!(contact.get_name(), "Real one"); assert_eq!(contact.get_addr(), "one@eins.org"); assert!(!contact.is_blocked()); @@ -1312,10 +1326,11 @@ mod tests { // check third added contact (contact without name) let (contact_id, sth_modified) = Contact::add_or_lookup(&t.ctx, "", "three@drei.sam", Origin::IncomingUnknownTo) + .await .unwrap(); assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL); assert_eq!(sth_modified, Modifier::None); - let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap(); + let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap(); assert_eq!(contact.get_name(), ""); assert_eq!(contact.get_display_name(), "three@drei.sam"); assert_eq!(contact.get_addr(), "three@drei.sam"); @@ -1328,10 +1343,11 @@ mod tests { "three@drei.sam", Origin::IncomingUnknownFrom, ) + .await .unwrap(); assert_eq!(contact_id, contact_id_test); assert_eq!(sth_modified, Modifier::Modified); - let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap(); + let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap(); assert_eq!(contact.get_name_n_addr(), "m. serious (three@drei.sam)"); assert!(!contact.is_blocked()); @@ -1342,25 +1358,31 @@ mod tests { "three@drei.sam", Origin::ManuallyCreated, ) + .await .unwrap(); assert_eq!(contact_id, contact_id_test); assert_eq!(sth_modified, Modifier::Modified); - let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap(); + let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap(); assert_eq!(contact.get_authname(), "m. serious"); assert_eq!(contact.get_name_n_addr(), "schnucki (three@drei.sam)"); assert!(!contact.is_blocked()); // check SELF - let contact = Contact::load_from_db(&t.ctx, DC_CONTACT_ID_SELF).unwrap(); + let contact = Contact::load_from_db(&t.ctx, DC_CONTACT_ID_SELF) + .await + .unwrap(); assert_eq!(DC_CONTACT_ID_SELF, 1); - assert_eq!(contact.get_name(), t.ctx.stock_str(StockMessage::SelfMsg)); + assert_eq!( + contact.get_name(), + t.ctx.stock_str(StockMessage::SelfMsg).await + ); assert_eq!(contact.get_addr(), ""); // we're not configured assert!(!contact.is_blocked()); } - #[test] - fn test_remote_authnames() { - let t = dummy_context(); + #[async_std::test] + async fn test_remote_authnames() { + let t = dummy_context().await; // incoming mail `From: bob1 ` - this should init authname and name let (contact_id, sth_modified) = Contact::add_or_lookup( @@ -1369,10 +1391,11 @@ mod tests { "bob@example.org", Origin::IncomingUnknownFrom, ) + .await .unwrap(); assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL); assert_eq!(sth_modified, Modifier::Created); - let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap(); + let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap(); assert_eq!(contact.get_authname(), "bob1"); assert_eq!(contact.get_name(), "bob1"); assert_eq!(contact.get_display_name(), "bob1"); @@ -1384,18 +1407,21 @@ mod tests { "bob@example.org", Origin::IncomingUnknownFrom, ) + .await .unwrap(); assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL); assert_eq!(sth_modified, Modifier::Modified); - let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap(); + let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap(); assert_eq!(contact.get_authname(), "bob2"); assert_eq!(contact.get_name(), "bob2"); assert_eq!(contact.get_display_name(), "bob2"); // manually edit name to "bob3" - authname should be still be "bob2" a given in `From:` above - let contact_id = Contact::create(&t.ctx, "bob3", "bob@example.org").unwrap(); + let contact_id = Contact::create(&t.ctx, "bob3", "bob@example.org") + .await + .unwrap(); assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL); - let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap(); + let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap(); assert_eq!(contact.get_authname(), "bob2"); assert_eq!(contact.get_name(), "bob3"); assert_eq!(contact.get_display_name(), "bob3"); @@ -1407,23 +1433,26 @@ mod tests { "bob@example.org", Origin::IncomingUnknownFrom, ) + .await .unwrap(); assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL); assert_eq!(sth_modified, Modifier::Modified); - let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap(); + let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap(); assert_eq!(contact.get_authname(), "bob4"); assert_eq!(contact.get_name(), "bob3"); assert_eq!(contact.get_display_name(), "bob3"); } - #[test] - fn test_remote_authnames_create_empty() { - let t = dummy_context(); + #[async_std::test] + async fn test_remote_authnames_create_empty() { + let t = dummy_context().await; // manually create "claire@example.org" without a given name - let contact_id = Contact::create(&t.ctx, "", "claire@example.org").unwrap(); + let contact_id = Contact::create(&t.ctx, "", "claire@example.org") + .await + .unwrap(); assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL); - let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap(); + let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap(); assert_eq!(contact.get_authname(), ""); assert_eq!(contact.get_name(), ""); assert_eq!(contact.get_display_name(), "claire@example.org"); @@ -1435,10 +1464,11 @@ mod tests { "claire@example.org", Origin::IncomingUnknownFrom, ) + .await .unwrap(); assert_eq!(contact_id, contact_id_same); assert_eq!(sth_modified, Modifier::Modified); - let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap(); + let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap(); assert_eq!(contact.get_authname(), "claire1"); assert_eq!(contact.get_name(), "claire1"); assert_eq!(contact.get_display_name(), "claire1"); @@ -1450,22 +1480,25 @@ mod tests { "claire@example.org", Origin::IncomingUnknownFrom, ) + .await .unwrap(); assert_eq!(contact_id, contact_id_same); assert_eq!(sth_modified, Modifier::Modified); - let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap(); + let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap(); assert_eq!(contact.get_authname(), "claire2"); assert_eq!(contact.get_name(), "claire2"); assert_eq!(contact.get_display_name(), "claire2"); } - #[test] - fn test_remote_authnames_edit_empty() { - let t = dummy_context(); + #[async_std::test] + async fn test_remote_authnames_edit_empty() { + let t = dummy_context().await; // manually create "dave@example.org" - let contact_id = Contact::create(&t.ctx, "dave1", "dave@example.org").unwrap(); - let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap(); + let contact_id = Contact::create(&t.ctx, "dave1", "dave@example.org") + .await + .unwrap(); + let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap(); assert_eq!(contact.get_authname(), ""); assert_eq!(contact.get_name(), "dave1"); assert_eq!(contact.get_display_name(), "dave1"); @@ -1477,15 +1510,18 @@ mod tests { "dave@example.org", Origin::IncomingUnknownFrom, ) + .await .unwrap(); - let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap(); + let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap(); assert_eq!(contact.get_authname(), "dave2"); assert_eq!(contact.get_name(), "dave1"); assert_eq!(contact.get_display_name(), "dave1"); // manually clear the name - Contact::create(&t.ctx, "", "dave@example.org").unwrap(); - let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap(); + Contact::create(&t.ctx, "", "dave@example.org") + .await + .unwrap(); + let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap(); assert_eq!(contact.get_authname(), "dave2"); assert_eq!(contact.get_name(), "dave2"); assert_eq!(contact.get_display_name(), "dave2"); diff --git a/src/context.rs b/src/context.rs index e9790079a..7a7408f63 100644 --- a/src/context.rs +++ b/src/context.rs @@ -3,7 +3,9 @@ use std::collections::HashMap; use std::ffi::OsString; use std::path::{Path, PathBuf}; -use std::sync::{atomic::AtomicBool, Arc, Mutex, RwLock}; +use std::sync::atomic::AtomicBool; + +use async_std::sync::{Arc, Mutex, RwLock}; use crate::chat::*; use crate::config::Config; @@ -156,14 +158,14 @@ impl Context { * Ongoing process allocation/free/check ******************************************************************************/ - pub fn alloc_ongoing(&self) -> bool { - if self.has_ongoing() { + pub async fn alloc_ongoing(&self) -> bool { + if self.has_ongoing().await { warn!(self, "There is already another ongoing process running.",); false } else { let s_a = self.running_state.clone(); - let mut s = s_a.write().unwrap(); + let mut s = s_a.write().await; s.ongoing_running = true; s.shall_stop_ongoing = false; @@ -172,25 +174,25 @@ impl Context { } } - pub fn free_ongoing(&self) { + pub async fn free_ongoing(&self) { let s_a = self.running_state.clone(); - let mut s = s_a.write().unwrap(); + let mut s = s_a.write().await; s.ongoing_running = false; s.shall_stop_ongoing = true; } - pub fn has_ongoing(&self) -> bool { + pub async fn has_ongoing(&self) -> bool { let s_a = self.running_state.clone(); - let s = s_a.read().unwrap(); + let s = s_a.read().await; s.ongoing_running || !s.shall_stop_ongoing } /// Signal an ongoing process to stop. - pub fn stop_ongoing(&self) { + pub async fn stop_ongoing(&self) { let s_a = self.running_state.clone(); - let mut s = s_a.write().unwrap(); + let mut s = s_a.write().await; if s.ongoing_running && !s.shall_stop_ongoing { info!(self, "Signaling the ongoing process to stop ASAP.",); @@ -200,12 +202,8 @@ impl Context { }; } - pub fn shall_stop_ongoing(&self) -> bool { - self.running_state - .clone() - .read() - .unwrap() - .shall_stop_ongoing + pub async fn shall_stop_ongoing(&self) -> bool { + self.running_state.clone().read().await.shall_stop_ongoing } /******************************************************************************* @@ -218,8 +216,8 @@ impl Context { let l2 = LoginParam::from_database(self, "configured_").await; let displayname = self.get_config(Config::Displayname).await; let chats = get_chat_cnt(self).await as usize; - let real_msgs = message::get_real_msg_cnt(self) as usize; - let deaddrop_msgs = message::get_deaddrop_msg_cnt(self) as usize; + let real_msgs = message::get_real_msg_cnt(self).await as usize; + let deaddrop_msgs = message::get_deaddrop_msg_cnt(self).await as usize; let contacts = Contact::get_real_cnt(self).await as usize; let is_configured = self.get_config_int(Config::Configured).await; let dbversion = self @@ -234,16 +232,12 @@ impl Context { let prv_key_cnt: Option = self .sql - .query_get_value(self, "SELECT COUNT(*) FROM keypairs;", rusqlite::NO_PARAMS) + .query_get_value(self, "SELECT COUNT(*) FROM keypairs;", paramsv![]) .await; let pub_key_cnt: Option = self .sql - .query_get_value( - self, - "SELECT COUNT(*) FROM acpeerstates;", - rusqlite::NO_PARAMS, - ) + .query_get_value(self, "SELECT COUNT(*) FROM acpeerstates;", paramsv![]) .await; let fingerprint_str = @@ -316,7 +310,7 @@ impl Context { } pub async fn get_fresh_msgs(&self) -> Vec { - let show_deaddrop = 0; + let show_deaddrop: i32 = 0; self.sql .query_map( concat!( @@ -333,7 +327,7 @@ impl Context { " AND (c.blocked=0 OR c.blocked=?)", " ORDER BY m.timestamp DESC,m.id DESC;" ), - &[10, 9, if 0 != show_deaddrop { 2 } else { 0 }], + paramsv![10, 9, if 0 != show_deaddrop { 2 } else { 0 }], |row| row.get::<_, MsgId>(0), |rows| { let mut ret = Vec::new(); @@ -388,7 +382,7 @@ impl Context { self.sql .query_map( query, - params![chat_id, &strLikeInText, &strLikeBeg], + paramsv![chat_id, strLikeInText, strLikeBeg], |row| row.get::<_, MsgId>("id"), |rows| { let mut ret = Vec::new(); @@ -475,7 +469,7 @@ impl Drop for Context { info!(self, "disconnecting SMTP"); self.smtp.disconnect().await; - self.sql.close(self); + self.sql.close(self).await; }); } } @@ -519,79 +513,85 @@ mod tests { use crate::test_utils::*; - #[test] - fn test_wrong_db() { + #[async_std::test] + async fn test_wrong_db() { let tmp = tempfile::tempdir().unwrap(); let dbfile = tmp.path().join("db.sqlite"); std::fs::write(&dbfile, b"123").unwrap(); - let res = Context::new(Box::new(|_, _| ()), "FakeOs".into(), dbfile); + let res = Context::new(Box::new(|_, _| ()), "FakeOs".into(), dbfile).await; assert!(res.is_err()); } - #[test] - fn test_get_fresh_msgs() { - let t = dummy_context(); - let fresh = t.ctx.get_fresh_msgs(); + #[async_std::test] + async fn test_get_fresh_msgs() { + let t = dummy_context().await; + let fresh = t.ctx.get_fresh_msgs().await; assert!(fresh.is_empty()) } - #[test] - fn test_blobdir_exists() { + #[async_std::test] + async fn test_blobdir_exists() { let tmp = tempfile::tempdir().unwrap(); let dbfile = tmp.path().join("db.sqlite"); - Context::new(Box::new(|_, _| ()), "FakeOS".into(), dbfile).unwrap(); + Context::new(Box::new(|_, _| ()), "FakeOS".into(), dbfile) + .await + .unwrap(); let blobdir = tmp.path().join("db.sqlite-blobs"); assert!(blobdir.is_dir()); } - #[test] - fn test_wrong_blogdir() { + #[async_std::test] + async fn test_wrong_blogdir() { let tmp = tempfile::tempdir().unwrap(); let dbfile = tmp.path().join("db.sqlite"); let blobdir = tmp.path().join("db.sqlite-blobs"); std::fs::write(&blobdir, b"123").unwrap(); - let res = Context::new(Box::new(|_, _| ()), "FakeOS".into(), dbfile); + let res = Context::new(Box::new(|_, _| ()), "FakeOS".into(), dbfile).await; assert!(res.is_err()); } - #[test] - fn test_sqlite_parent_not_exists() { + #[async_std::test] + async fn test_sqlite_parent_not_exists() { let tmp = tempfile::tempdir().unwrap(); let subdir = tmp.path().join("subdir"); let dbfile = subdir.join("db.sqlite"); let dbfile2 = dbfile.clone(); - Context::new(Box::new(|_, _| ()), "FakeOS".into(), dbfile).unwrap(); + Context::new(Box::new(|_, _| ()), "FakeOS".into(), dbfile) + .await + .unwrap(); assert!(subdir.is_dir()); assert!(dbfile2.is_file()); } - #[test] - fn test_with_empty_blobdir() { + #[async_std::test] + async fn test_with_empty_blobdir() { let tmp = tempfile::tempdir().unwrap(); let dbfile = tmp.path().join("db.sqlite"); let blobdir = PathBuf::new(); - let res = Context::with_blobdir(Box::new(|_, _| ()), "FakeOS".into(), dbfile, blobdir); + let res = + Context::with_blobdir(Box::new(|_, _| ()), "FakeOS".into(), dbfile, blobdir).await; assert!(res.is_err()); } - #[test] - fn test_with_blobdir_not_exists() { + #[async_std::test] + async fn test_with_blobdir_not_exists() { let tmp = tempfile::tempdir().unwrap(); let dbfile = tmp.path().join("db.sqlite"); let blobdir = tmp.path().join("blobs"); - let res = Context::with_blobdir(Box::new(|_, _| ()), "FakeOS".into(), dbfile, blobdir); + let res = + Context::with_blobdir(Box::new(|_, _| ()), "FakeOS".into(), dbfile, blobdir).await; assert!(res.is_err()); } - #[test] - fn no_crashes_on_context_deref() { - let t = dummy_context(); + #[async_std::test] + async fn no_crashes_on_context_deref() { + let t = dummy_context().await; std::mem::drop(t.ctx); } #[async_std::test] async fn test_get_info() { - let t = dummy_context(); + let t = dummy_context().await; let info = t.ctx.get_info().await; assert!(info.get("database_dir").is_some()); diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index d8c7b3cfd..91c4b3f23 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -96,7 +96,7 @@ pub async fn dc_receive_imf( // https://github.com/deltachat/deltachat-core/issues/150) let (from_id, from_id_blocked, incoming_origin) = if let Some(field_from) = mime_parser.get(HeaderDef::From_) { - from_field_to_contact_id(context, field_from)? + from_field_to_contact_id(context, field_from).await? } else { (0, false, Origin::Unknown) }; @@ -105,17 +105,20 @@ pub async fn dc_receive_imf( let mut to_ids = ContactIds::new(); for header_def in &[HeaderDef::To, HeaderDef::Cc] { if let Some(field) = mime_parser.get(header_def.clone()) { - to_ids.extend(&dc_add_or_lookup_contacts_by_address_list( - context, - &field, - if !incoming { - Origin::OutgoingTo - } else if incoming_origin.is_known() { - Origin::IncomingTo - } else { - Origin::IncomingUnknownTo - }, - )?); + to_ids.extend( + &dc_add_or_lookup_contacts_by_address_list( + context, + &field, + if !incoming { + Origin::OutgoingTo + } else if incoming_origin.is_known() { + Origin::IncomingTo + } else { + Origin::IncomingUnknownTo + }, + ) + .await?, + ); } } @@ -178,7 +181,8 @@ pub async fn dc_receive_imf( from_id, insert_msg_id, hidden, - ); + ) + .await; } if let Some(avatar_action) = &mime_parser.user_avatar { @@ -225,7 +229,7 @@ pub async fn dc_receive_imf( /// Converts "From" field to contact id. /// /// Also returns whether it is blocked or not and its origin. -pub fn from_field_to_contact_id( +pub async fn from_field_to_contact_id( context: &Context, field_from: &str, ) -> Result<(u32, bool, Origin)> { @@ -233,7 +237,8 @@ pub fn from_field_to_contact_id( context, &field_from, Origin::IncomingUnknownFrom, - )?; + ) + .await?; if from_ids.contains(&DC_CONTACT_ID_SELF) { Ok((DC_CONTACT_ID_SELF, false, Origin::OutgoingBcc)) @@ -248,7 +253,7 @@ pub fn from_field_to_contact_id( let mut from_id_blocked = false; let mut incoming_origin = Origin::Unknown; - if let Ok(contact) = Contact::load_from_db(context, from_id) { + if let Ok(contact) = Contact::load_from_db(context, from_id).await { from_id_blocked = contact.blocked; incoming_origin = contact.origin; } @@ -297,10 +302,11 @@ async fn add_parts( // (if the mail was moved around) and finish. (we may get a mail twice eg. if it is // moved between folders. make sure, this check is done eg. before securejoin-processing) */ if let Ok((old_server_folder, old_server_uid, _)) = - message::rfc724_mid_exists(context, &rfc724_mid) + message::rfc724_mid_exists(context, &rfc724_mid).await { if old_server_folder != server_folder.as_ref() || old_server_uid != server_uid { - message::update_server_uid(context, &rfc724_mid, server_folder.as_ref(), server_uid); + message::update_server_uid(context, &rfc724_mid, server_folder.as_ref(), server_uid) + .await; } bail!("Message already in DB"); @@ -308,7 +314,7 @@ async fn add_parts( let mut msgrmsg = if mime_parser.has_chat_version() { MessengerMessage::Yes - } else if is_reply_to_messenger_message(context, mime_parser) { + } else if is_reply_to_messenger_message(context, mime_parser).await { MessengerMessage::Reply } else { MessengerMessage::No @@ -368,8 +374,8 @@ async fn add_parts( } Err(err) => { *hidden = true; - context.bob.write().unwrap().status = 0; // secure-join failed - context.stop_ongoing(); + context.bob.write().await.status = 0; // secure-join failed + context.stop_ongoing().await; error!(context, "Error in Secure-Join message handling: {}", err); } } @@ -408,7 +414,7 @@ async fn add_parts( && chat_id_blocked != Blocked::Not && create_blocked == Blocked::Not { - new_chat_id.unblock(context); + new_chat_id.unblock(context).await; chat_id_blocked = Blocked::Not; } } @@ -442,12 +448,12 @@ async fn add_parts( } if !chat_id.is_unset() && Blocked::Not != chat_id_blocked { if Blocked::Not == create_blocked { - chat_id.unblock(context); + chat_id.unblock(context).await; chat_id_blocked = Blocked::Not; - } else if is_reply_to_known_message(context, mime_parser) { + } else if is_reply_to_known_message(context, mime_parser).await { // we do not want any chat to be created implicitly. Because of the origin-scale-up, // the contact requests will pop up and this should be just fine. - Contact::scaleup_origin_by_id(context, from_id, Origin::IncomingReplyTo); + Contact::scaleup_origin_by_id(context, from_id, Origin::IncomingReplyTo).await; info!( context, "Message is a reply to a known message, mark sender as known.", @@ -496,7 +502,7 @@ async fn add_parts( chat_id_blocked = new_chat_id_blocked; // automatically unblock chat when the user sends a message if !chat_id.is_unset() && chat_id_blocked != Blocked::Not { - new_chat_id.unblock(context); + new_chat_id.unblock(context).await; chat_id_blocked = Blocked::Not; } } @@ -518,7 +524,7 @@ async fn add_parts( && Blocked::Not != chat_id_blocked && Blocked::Not == create_blocked { - chat_id.unblock(context); + chat_id.unblock(context).await; chat_id_blocked = Blocked::Not; } } @@ -538,7 +544,7 @@ async fn add_parts( chat_id_blocked = bl; if !chat_id.is_unset() && Blocked::Not != chat_id_blocked { - chat_id.unblock(context); + chat_id.unblock(context).await; chat_id_blocked = Blocked::Not; } } @@ -557,7 +563,8 @@ async fn add_parts( &mut sort_timestamp, sent_timestamp, &mut rcvd_timestamp, - ); + ) + .await; // unarchive chat chat_id.unarchive(context).await?; @@ -613,9 +620,9 @@ async fn add_parts( .set_int(Param::Cmd, mime_parser.is_system_message as i32); } - stmt.execute(params![ + stmt.execute(paramsv![ rfc724_mid, - server_folder.as_ref(), + server_folder.as_ref().to_string(), server_uid as i32, *chat_id, from_id as i32, @@ -626,7 +633,7 @@ async fn add_parts( part.typ, state, msgrmsg, - &part.msg, + part.msg, // txt_raw might contain invalid utf8 txt_raw.unwrap_or_default(), part.param.to_string(), @@ -675,7 +682,7 @@ async fn add_parts( Ok(()) } -fn save_locations( +async fn save_locations( context: &Context, mime_parser: &MimeMessage, chat_id: ChatId, @@ -691,11 +698,14 @@ fn save_locations( if mime_parser.message_kml.is_some() { let locations = &mime_parser.message_kml.as_ref().unwrap().locations; - let newest_location_id = - location::save(context, chat_id, from_id, locations, true).unwrap_or_default(); + let newest_location_id = location::save(context, chat_id, from_id, locations, true) + .await + .unwrap_or_default(); if 0 != newest_location_id && !hidden - && location::set_msg_location_id(context, insert_msg_id, newest_location_id).is_ok() + && location::set_msg_location_id(context, insert_msg_id, newest_location_id) + .await + .is_ok() { location_id_written = true; send_event = true; @@ -704,18 +714,21 @@ fn save_locations( if mime_parser.location_kml.is_some() { if let Some(ref addr) = mime_parser.location_kml.as_ref().unwrap().addr { - if let Ok(contact) = Contact::get_by_id(context, from_id) { + if let Ok(contact) = Contact::get_by_id(context, from_id).await { if contact.get_addr().to_lowercase() == addr.to_lowercase() { let locations = &mime_parser.location_kml.as_ref().unwrap().locations; let newest_location_id = location::save(context, chat_id, from_id, locations, false) + .await .unwrap_or_default(); if newest_location_id != 0 && !hidden && !location_id_written { if let Err(err) = location::set_msg_location_id( context, insert_msg_id, newest_location_id, - ) { + ) + .await + { error!(context, "Failed to set msg_location_id: {:?}", err); } } @@ -730,7 +743,7 @@ fn save_locations( } #[allow(clippy::too_many_arguments)] -fn calc_timestamps( +async fn calc_timestamps( context: &Context, chat_id: ChatId, from_id: u32, @@ -747,19 +760,22 @@ fn calc_timestamps( } *sort_timestamp = message_timestamp; if is_fresh_msg { - let last_msg_time: Option = context.sql.query_get_value( - context, - "SELECT MAX(timestamp) FROM msgs WHERE chat_id=? and from_id!=? AND timestamp>=?", - params![chat_id, from_id as i32, *sort_timestamp], - ); + let last_msg_time: Option = context + .sql + .query_get_value( + context, + "SELECT MAX(timestamp) FROM msgs WHERE chat_id=? and from_id!=? AND timestamp>=?", + paramsv![chat_id, from_id as i32, *sort_timestamp], + ) + .await; if let Some(last_msg_time) = last_msg_time { if last_msg_time > 0 && *sort_timestamp <= last_msg_time { *sort_timestamp = last_msg_time + 1; } } } - if *sort_timestamp >= dc_smeared_time(context) { - *sort_timestamp = dc_create_smeared_timestamp(context); + if *sort_timestamp >= dc_smeared_time(context).await { + *sort_timestamp = dc_create_smeared_timestamp(context).await; } } @@ -792,8 +808,9 @@ async fn create_or_lookup_group( let mut better_msg: String = From::from(""); if mime_parser.is_system_message == SystemMessage::LocationStreamingEnabled { - better_msg = - context.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", from_id as u32); + better_msg = context + .stock_system_msg(StockMessage::MsgLocationEnabled, "", "", from_id as u32) + .await; set_better_msg(mime_parser, &better_msg); } @@ -823,6 +840,7 @@ async fn create_or_lookup_group( from_id, to_ids, ) + .await .map_err(|err| { info!(context, "could not create adhoc-group: {:?}", err); err @@ -841,41 +859,47 @@ async fn create_or_lookup_group( let left_group = Contact::lookup_id_by_addr(context, X_MrRemoveFromGrp.as_ref().unwrap()) .await == from_id as u32; - better_msg = context.stock_system_msg( - if left_group { - StockMessage::MsgGroupLeft - } else { - StockMessage::MsgDelMember - }, - X_MrRemoveFromGrp.as_ref().unwrap(), - "", - from_id as u32, - ) + better_msg = context + .stock_system_msg( + if left_group { + StockMessage::MsgGroupLeft + } else { + StockMessage::MsgDelMember + }, + X_MrRemoveFromGrp.as_ref().unwrap(), + "", + from_id as u32, + ) + .await } else { let field = mime_parser.get(HeaderDef::ChatGroupMemberAdded).cloned(); if let Some(optional_field) = field { mime_parser.is_system_message = SystemMessage::MemberAddedToGroup; - better_msg = context.stock_system_msg( - StockMessage::MsgAddMember, - &optional_field, - "", - from_id as u32, - ); + better_msg = context + .stock_system_msg( + StockMessage::MsgAddMember, + &optional_field, + "", + from_id as u32, + ) + .await; X_MrAddToGrp = Some(optional_field); } else { let field = mime_parser.get(HeaderDef::ChatGroupNameChanged); if let Some(field) = field { X_MrGrpNameChanged = true; - better_msg = context.stock_system_msg( - StockMessage::MsgGrpName, - field, - if let Some(ref name) = grpname { - name - } else { - "" - }, - from_id as u32, - ); + better_msg = context + .stock_system_msg( + StockMessage::MsgGrpName, + field, + if let Some(ref name) = grpname { + name + } else { + "" + }, + from_id as u32, + ) + .await; mime_parser.is_system_message = SystemMessage::GroupNameChanged; } else if let Some(value) = mime_parser.get(HeaderDef::ChatContent) { @@ -884,15 +908,17 @@ async fn create_or_lookup_group( // this is just an explicit message containing the group-avatar, // apart from that, the group-avatar is send along with various other messages mime_parser.is_system_message = SystemMessage::GroupImageChanged; - better_msg = context.stock_system_msg( - match avatar_action { - AvatarAction::Delete => StockMessage::MsgGrpImgDeleted, - AvatarAction::Change(_) => StockMessage::MsgGrpImgChanged, - }, - "", - "", - from_id as u32, - ) + better_msg = context + .stock_system_msg( + match avatar_action { + AvatarAction::Delete => StockMessage::MsgGrpImgDeleted, + AvatarAction::Change(_) => StockMessage::MsgGrpImgChanged, + }, + "", + "", + from_id as u32, + ) + .await } } } @@ -907,7 +933,7 @@ async fn create_or_lookup_group( if !chat_id.is_error() { if chat_id_verified { if let Err(err) = - check_verified_properties(context, mime_parser, from_id as u32, to_ids) + check_verified_properties(context, mime_parser, from_id as u32, to_ids).await { warn!(context, "verification problem: {}", err); let s = format!("{}. See 'Info' for more details", err); @@ -921,7 +947,7 @@ async fn create_or_lookup_group( // but still show the message as part of the chat. // After all, the sender has a reference/in-reply-to that // points to this chat. - let s = context.stock_str(StockMessage::UnknownSenderForChat); + let s = context.stock_str(StockMessage::UnknownSenderForChat).await; mime_parser.repl_msg_by_error(s.to_string()); } } @@ -948,7 +974,7 @@ async fn create_or_lookup_group( // group does not exist but should be created let create_verified = if mime_parser.get(HeaderDef::ChatVerified).is_some() { if let Err(err) = - check_verified_properties(context, mime_parser, from_id as u32, to_ids) + check_verified_properties(context, mime_parser, from_id as u32, to_ids).await { warn!(context, "verification problem: {}", err); let s = format!("{}. See 'Info' for more details", err); @@ -989,6 +1015,7 @@ async fn create_or_lookup_group( from_id, to_ids, ) + .await .map_err(|err| { warn!(context, "failed to create ad-hoc group: {:?}", err); err @@ -1019,7 +1046,7 @@ async fn create_or_lookup_group( .sql .execute( "UPDATE chats SET name=? WHERE id=?;", - params![grpname, chat_id], + paramsv![grpname.to_string(), chat_id], ) .await .is_ok() @@ -1061,7 +1088,7 @@ async fn create_or_lookup_group( if !Contact::addr_equals_contact(context, &self_addr, to_id).await && !chat::is_contact_in_chat(context, chat_id, to_id).await { - chat::add_to_chat_contacts_table(context, chat_id, to_id); + chat::add_to_chat_contacts_table(context, chat_id, to_id).await; } } send_EVENT_CHAT_MODIFIED = true; @@ -1069,7 +1096,7 @@ async fn create_or_lookup_group( let contact_id = Contact::lookup_id_by_addr(context, removed_addr).await; if contact_id != 0 { info!(context, "remove {:?} from chat id={}", contact_id, chat_id); - chat::remove_from_chat_contacts_table(context, chat_id, contact_id); + chat::remove_from_chat_contacts_table(context, chat_id, contact_id).await; } send_EVENT_CHAT_MODIFIED = true; } @@ -1091,7 +1118,7 @@ fn extract_grpid(mime_parser: &MimeMessage, headerdef: HeaderDef) -> Option<&str } /// Handle groups for received messages, return chat_id/Blocked status on success -fn create_or_lookup_adhoc_group( +async fn create_or_lookup_adhoc_group( context: &Context, mime_parser: &MimeMessage, allow_creation: bool, @@ -1126,12 +1153,14 @@ fn create_or_lookup_adhoc_group( return Ok((ChatId::new(0), Blocked::Not)); } - let chat_ids = search_chat_ids_by_contact_ids(context, &member_ids)?; + let chat_ids = search_chat_ids_by_contact_ids(context, &member_ids).await?; if !chat_ids.is_empty() { let chat_ids_str = join(chat_ids.iter().map(|x| x.to_string()), ","); - let res = context.sql.query_row( - format!( - "SELECT c.id, + let res = context + .sql + .query_row( + format!( + "SELECT c.id, c.blocked FROM chats c LEFT JOIN msgs m @@ -1140,16 +1169,17 @@ fn create_or_lookup_adhoc_group( ORDER BY m.timestamp DESC, m.id DESC LIMIT 1;", - chat_ids_str - ), - params![], - |row| { - Ok(( - row.get::<_, ChatId>(0)?, - row.get::<_, Option>(1)?.unwrap_or_default(), - )) - }, - ); + chat_ids_str + ), + paramsv![], + |row| { + Ok(( + row.get::<_, ChatId>(0)?, + row.get::<_, Option>(1)?.unwrap_or_default(), + )) + }, + ) + .await; if let Ok((id, id_blocked)) = res { /* success, chat found */ @@ -1167,7 +1197,7 @@ fn create_or_lookup_adhoc_group( // create a new ad-hoc group // - there is no need to check if this group exists; otherwise we would have caught it above - let grpid = create_adhoc_grp_id(context, &member_ids); + let grpid = create_adhoc_grp_id(context, &member_ids).await; if grpid.is_empty() { warn!( context, @@ -1176,9 +1206,10 @@ fn create_or_lookup_adhoc_group( return Ok((ChatId::new(0), Blocked::Not)); } // use subject as initial chat name - let grpname = mime_parser.get_subject().unwrap_or_else(|| { - context.stock_string_repl_int(StockMessage::Member, member_ids.len() as i32) - }); + let default_name = context + .stock_string_repl_int(StockMessage::Member, member_ids.len() as i32) + .await; + let grpname = mime_parser.get_subject().unwrap_or_else(|| default_name); // create group record let new_chat_id: ChatId = create_group_record( @@ -1187,9 +1218,10 @@ fn create_or_lookup_adhoc_group( grpname, create_blocked, VerifiedStatus::Unverified, - ); + ) + .await; for &member_id in &member_ids { - chat::add_to_chat_contacts_table(context, new_chat_id, member_id); + chat::add_to_chat_contacts_table(context, new_chat_id, member_id).await; } context.call_cb(Event::ChatModified(new_chat_id)); @@ -1206,7 +1238,7 @@ async fn create_group_record( ) -> ChatId { if context.sql.execute( "INSERT INTO chats (type, name, grpid, blocked, created_timestamp) VALUES(?, ?, ?, ?, ?);", - params![ + paramsv![ if VerifiedStatus::Unverified != create_verified { Chattype::VerifiedGroup } else { @@ -1245,7 +1277,7 @@ async fn create_group_record( chat_id } -fn create_adhoc_grp_id(context: &Context, member_ids: &[u32]) -> String { +async fn create_adhoc_grp_id(context: &Context, member_ids: &[u32]) -> String { /* algorithm: - sort normalized, lowercased, e-mail addresses alphabetically - put all e-mail addresses into a single string, separate the address by a single comma @@ -1255,6 +1287,7 @@ fn create_adhoc_grp_id(context: &Context, member_ids: &[u32]) -> String { let member_ids_str = join(member_ids.iter().map(|x| x.to_string()), ","); let member_cs = context .get_config(Config::ConfiguredAddr) + .await .unwrap_or_else(|| "no-self".to_string()) .to_lowercase(); @@ -1265,7 +1298,7 @@ fn create_adhoc_grp_id(context: &Context, member_ids: &[u32]) -> String { "SELECT addr FROM contacts WHERE id IN({}) AND id!=1", // 1=DC_CONTACT_ID_SELF member_ids_str ), - params![], + paramsv![], |row| row.get::<_, String>(0), |rows| { let mut addrs = rows.collect::, _>>()?; @@ -1278,6 +1311,7 @@ fn create_adhoc_grp_id(context: &Context, member_ids: &[u32]) -> String { Ok(acc) }, ) + .await .unwrap_or_else(|_| member_cs); hex_hash(&members) @@ -1289,7 +1323,7 @@ fn hex_hash(s: impl AsRef) -> String { hex::encode(&result[..8]) } -fn search_chat_ids_by_contact_ids( +async fn search_chat_ids_by_contact_ids( context: &Context, unsorted_contact_ids: &[u32], ) -> Result> { @@ -1318,7 +1352,7 @@ fn search_chat_ids_by_contact_ids( ORDER BY cc.chat_id, cc.contact_id;", // 1=DC_CONTACT_ID_SELF contact_ids_str ), - params![], + paramsv![], |row| Ok((row.get::<_, ChatId>(0)?, row.get::<_, u32>(1)?)), |rows| { let mut last_chat_id = ChatId::new(0); @@ -1347,20 +1381,20 @@ fn search_chat_ids_by_contact_ids( } Ok(()) } - )?; + ).await?; } } Ok(chat_ids) } -fn check_verified_properties( +async fn check_verified_properties( context: &Context, mimeparser: &MimeMessage, from_id: u32, to_ids: &ContactIds, ) -> Result<()> { - let contact = Contact::load_from_db(context, from_id)?; + let contact = Contact::load_from_db(context, from_id).await?; ensure!(mimeparser.was_encrypted(), "This message is not encrypted."); @@ -1369,10 +1403,10 @@ fn check_verified_properties( // this check is skipped for SELF as there is no proper SELF-peerstate // and results in group-splits otherwise. if from_id != DC_CONTACT_ID_SELF { - let peerstate = Peerstate::from_addr(context, &context.sql, contact.get_addr()); + let peerstate = Peerstate::from_addr(context, &context.sql, contact.get_addr()).await; if peerstate.is_none() - || contact.is_verified_ex(context, peerstate.as_ref()) + || contact.is_verified_ex(context, peerstate.as_ref()).await != VerifiedStatus::BidirectVerified { bail!( @@ -1398,29 +1432,32 @@ fn check_verified_properties( } let to_ids_str = join(to_ids.iter().map(|x| x.to_string()), ","); - let rows = context.sql.query_map( - format!( - "SELECT c.addr, LENGTH(ps.verified_key_fingerprint) FROM contacts c \ + let rows = context + .sql + .query_map( + format!( + "SELECT c.addr, LENGTH(ps.verified_key_fingerprint) FROM contacts c \ LEFT JOIN acpeerstates ps ON c.addr=ps.addr WHERE c.id IN({}) ", - to_ids_str - ), - params![], - |row| Ok((row.get::<_, String>(0)?, row.get::<_, i32>(1).unwrap_or(0))), - |rows| { - rows.collect::, _>>() - .map_err(Into::into) - }, - )?; + to_ids_str + ), + paramsv![], + |row| Ok((row.get::<_, String>(0)?, row.get::<_, i32>(1).unwrap_or(0))), + |rows| { + rows.collect::, _>>() + .map_err(Into::into) + }, + ) + .await?; for (to_addr, _is_verified) in rows.into_iter() { info!( context, "check_verified_properties: {:?} self={:?}", to_addr, - context.is_self_addr(&to_addr) + context.is_self_addr(&to_addr).await ); let mut is_verified = _is_verified != 0; - let peerstate = Peerstate::from_addr(context, &context.sql, &to_addr); + let peerstate = Peerstate::from_addr(context, &context.sql, &to_addr).await; // mark gossiped keys (if any) as verified if mimeparser.gossipped_addr.contains(&to_addr) { @@ -1442,7 +1479,7 @@ fn check_verified_properties( &fp, PeerstateVerifiedStatus::BidirectVerified, ); - peerstate.save_to_db(&context.sql, false)?; + peerstate.save_to_db(&context.sql, false).await?; is_verified = true; } } @@ -1468,18 +1505,18 @@ fn set_better_msg(mime_parser: &mut MimeMessage, better_msg: impl AsRef) { } } -fn is_reply_to_known_message(context: &Context, mime_parser: &MimeMessage) -> bool { +async fn is_reply_to_known_message(context: &Context, mime_parser: &MimeMessage) -> bool { /* check if the message is a reply to a known message; the replies are identified by the Message-ID from `In-Reply-To`/`References:` (to support non-Delta-Clients) */ if let Some(field) = mime_parser.get(HeaderDef::InReplyTo) { - if is_known_rfc724_mid_in_list(context, &field) { + if is_known_rfc724_mid_in_list(context, &field).await { return true; } } if let Some(field) = mime_parser.get(HeaderDef::References) { - if is_known_rfc724_mid_in_list(context, &field) { + if is_known_rfc724_mid_in_list(context, &field).await { return true; } } @@ -1487,14 +1524,14 @@ fn is_reply_to_known_message(context: &Context, mime_parser: &MimeMessage) -> bo false } -fn is_known_rfc724_mid_in_list(context: &Context, mid_list: &str) -> bool { +async fn is_known_rfc724_mid_in_list(context: &Context, mid_list: &str) -> bool { if mid_list.is_empty() { return false; } if let Ok(ids) = mailparse::msgidparse(mid_list) { for id in ids.iter() { - if is_known_rfc724_mid(context, id) { + if is_known_rfc724_mid(context, id).await { return true; } } @@ -1504,7 +1541,7 @@ fn is_known_rfc724_mid_in_list(context: &Context, mid_list: &str) -> bool { } /// Check if a message is a reply to a known message (messenger or non-messenger). -fn is_known_rfc724_mid(context: &Context, rfc724_mid: &str) -> bool { +async fn is_known_rfc724_mid(context: &Context, rfc724_mid: &str) -> bool { context .sql .exists( @@ -1512,8 +1549,9 @@ fn is_known_rfc724_mid(context: &Context, rfc724_mid: &str) -> bool { LEFT JOIN chats c ON m.chat_id=c.id \ WHERE m.rfc724_mid=? \ AND m.chat_id>9 AND c.blocked=0;", - params![rfc724_mid], + paramsv![rfc724_mid], ) + .await .unwrap_or_default() } @@ -1522,15 +1560,15 @@ fn is_known_rfc724_mid(context: &Context, rfc724_mid: &str) -> bool { /// - checks also if any of the referenced IDs are send by a messenger /// - it is okay, if the referenced messages are moved to trash here /// - no check for the Chat-* headers (function is only called if it is no messenger message itself) -fn is_reply_to_messenger_message(context: &Context, mime_parser: &MimeMessage) -> bool { +async fn is_reply_to_messenger_message(context: &Context, mime_parser: &MimeMessage) -> bool { if let Some(value) = mime_parser.get(HeaderDef::InReplyTo) { - if is_msgrmsg_rfc724_mid_in_list(context, &value) { + if is_msgrmsg_rfc724_mid_in_list(context, &value).await { return true; } } if let Some(value) = mime_parser.get(HeaderDef::References) { - if is_msgrmsg_rfc724_mid_in_list(context, &value) { + if is_msgrmsg_rfc724_mid_in_list(context, &value).await { return true; } } @@ -1538,10 +1576,10 @@ fn is_reply_to_messenger_message(context: &Context, mime_parser: &MimeMessage) - false } -pub(crate) fn is_msgrmsg_rfc724_mid_in_list(context: &Context, mid_list: &str) -> bool { +pub(crate) async fn is_msgrmsg_rfc724_mid_in_list(context: &Context, mid_list: &str) -> bool { if let Ok(ids) = mailparse::msgidparse(mid_list) { for id in ids.iter() { - if is_msgrmsg_rfc724_mid(context, id) { + if is_msgrmsg_rfc724_mid(context, id).await { return true; } } @@ -1550,17 +1588,18 @@ pub(crate) fn is_msgrmsg_rfc724_mid_in_list(context: &Context, mid_list: &str) - } /// Check if a message is a reply to any messenger message. -fn is_msgrmsg_rfc724_mid(context: &Context, rfc724_mid: &str) -> bool { +async fn is_msgrmsg_rfc724_mid(context: &Context, rfc724_mid: &str) -> bool { context .sql .exists( "SELECT id FROM msgs WHERE rfc724_mid=? AND msgrmsg!=0 AND chat_id>9;", - params![rfc724_mid], + paramsv![rfc724_mid], ) + .await .unwrap_or_default() } -fn dc_add_or_lookup_contacts_by_address_list( +async fn dc_add_or_lookup_contacts_by_address_list( context: &Context, addr_list_raw: &str, origin: Origin, @@ -1576,21 +1615,22 @@ fn dc_add_or_lookup_contacts_by_address_list( for addr in addrs.iter() { match addr { mailparse::MailAddr::Single(info) => { - contact_ids.insert(add_or_lookup_contact_by_addr( - context, - &info.display_name, - &info.addr, - origin, - )?); + contact_ids.insert( + add_or_lookup_contact_by_addr(context, &info.display_name, &info.addr, origin) + .await?, + ); } mailparse::MailAddr::Group(infos) => { for info in &infos.addrs { - contact_ids.insert(add_or_lookup_contact_by_addr( - context, - &info.display_name, - &info.addr, - origin, - )?); + contact_ids.insert( + add_or_lookup_contact_by_addr( + context, + &info.display_name, + &info.addr, + origin, + ) + .await?, + ); } } } @@ -1600,13 +1640,13 @@ fn dc_add_or_lookup_contacts_by_address_list( } /// Add contacts to database on receiving messages. -fn add_or_lookup_contact_by_addr( +async fn add_or_lookup_contact_by_addr( context: &Context, display_name: &Option, addr: &str, origin: Origin, ) -> Result { - if context.is_self_addr(addr)? { + if context.is_self_addr(addr).await? { return Ok(DC_CONTACT_ID_SELF); } let display_name_normalized = display_name @@ -1615,7 +1655,7 @@ fn add_or_lookup_contact_by_addr( .unwrap_or_default(); let (row_id, _modified) = - Contact::add_or_lookup(context, display_name_normalized, addr, origin)?; + Contact::add_or_lookup(context, display_name_normalized, addr, origin).await?; ensure!(row_id > 0, "could not add contact: {:?}", addr); Ok(row_id) @@ -1650,31 +1690,35 @@ mod tests { assert_eq!(res, "b94d27b9934d3e08"); } - #[test] - fn test_grpid_simple() { - let context = dummy_context(); + #[async_std::test] + async fn test_grpid_simple() { + let context = dummy_context().await; let raw = b"From: hello\n\ Subject: outer-subject\n\ In-Reply-To: \n\ References: \n\ \n\ hello\x00"; - let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap(); + let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]) + .await + .unwrap(); assert_eq!(extract_grpid(&mimeparser, HeaderDef::InReplyTo), None); let grpid = Some("HcxyMARjyJy"); assert_eq!(extract_grpid(&mimeparser, HeaderDef::References), grpid); } - #[test] - fn test_grpid_from_multiple() { - let context = dummy_context(); + #[async_std::test] + async fn test_grpid_from_multiple() { + let context = dummy_context().await; let raw = b"From: hello\n\ Subject: outer-subject\n\ In-Reply-To: \n\ References: , \n\ \n\ hello\x00"; - let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap(); + let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]) + .await + .unwrap(); let grpid = Some("HcxyMARjyJy"); assert_eq!(extract_grpid(&mimeparser, HeaderDef::InReplyTo), grpid); assert_eq!(extract_grpid(&mimeparser, HeaderDef::References), grpid); diff --git a/src/dc_tools.rs b/src/dc_tools.rs index 0aa7bbeeb..1144a2b57 100644 --- a/src/dc_tools.rs +++ b/src/dc_tools.rs @@ -99,9 +99,9 @@ const MAX_SECONDS_TO_LEND_FROM_FUTURE: i64 = 5; // returns the currently smeared timestamp, // may be used to check if call to dc_create_smeared_timestamp() is needed or not. // the returned timestamp MUST NOT be used to be sent out or saved in the database! -pub(crate) fn dc_smeared_time(context: &Context) -> i64 { +pub(crate) async fn dc_smeared_time(context: &Context) -> i64 { let mut now = time(); - let ts = *context.last_smeared_timestamp.read().unwrap(); + let ts = *context.last_smeared_timestamp.read().await; if ts >= now { now = ts + 1; } @@ -110,11 +110,11 @@ pub(crate) fn dc_smeared_time(context: &Context) -> i64 { } // returns a timestamp that is guaranteed to be unique. -pub(crate) fn dc_create_smeared_timestamp(context: &Context) -> i64 { +pub(crate) async fn dc_create_smeared_timestamp(context: &Context) -> i64 { let now = time(); let mut ret = now; - let mut last_smeared_timestamp = context.last_smeared_timestamp.write().unwrap(); + let mut last_smeared_timestamp = context.last_smeared_timestamp.write().await; if ret <= *last_smeared_timestamp { ret = *last_smeared_timestamp + 1; if ret - now > MAX_SECONDS_TO_LEND_FROM_FUTURE { @@ -129,12 +129,12 @@ pub(crate) fn dc_create_smeared_timestamp(context: &Context) -> i64 { // creates `count` timestamps that are guaranteed to be unique. // the frist created timestamps is returned directly, // get the other timestamps just by adding 1..count-1 -pub(crate) fn dc_create_smeared_timestamps(context: &Context, count: usize) -> i64 { +pub(crate) async fn dc_create_smeared_timestamps(context: &Context, count: usize) -> i64 { let now = time(); let count = count as i64; let mut start = now + min(count, MAX_SECONDS_TO_LEND_FROM_FUTURE) - count; - let mut last_smeared_timestamp = context.last_smeared_timestamp.write().unwrap(); + let mut last_smeared_timestamp = context.last_smeared_timestamp.write().await; start = max(*last_smeared_timestamp + 1, start); *last_smeared_timestamp = start + count - 1; @@ -711,9 +711,9 @@ mod tests { } } - #[test] - fn test_file_handling() { - let t = dummy_context(); + #[async_std::test] + async fn test_file_handling() { + let t = dummy_context().await; let context = &t.ctx; let dc_file_exist = |ctx: &Context, fname: &str| { ctx.get_blobdir() @@ -784,15 +784,15 @@ mod tests { assert!(!listflags_has(listflags, DC_GCL_ADD_SELF)); } - #[test] - fn test_create_smeared_timestamp() { - let t = dummy_context(); + #[async_std::test] + async fn test_create_smeared_timestamp() { + let t = dummy_context().await; assert_ne!( - dc_create_smeared_timestamp(&t.ctx), - dc_create_smeared_timestamp(&t.ctx) + dc_create_smeared_timestamp(&t.ctx).await, + dc_create_smeared_timestamp(&t.ctx).await ); assert!( - dc_create_smeared_timestamp(&t.ctx) + dc_create_smeared_timestamp(&t.ctx).await >= SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .unwrap() @@ -800,17 +800,17 @@ mod tests { ); } - #[test] - fn test_create_smeared_timestamps() { - let t = dummy_context(); + #[async_std::test] + async fn test_create_smeared_timestamps() { + let t = dummy_context().await; let count = MAX_SECONDS_TO_LEND_FROM_FUTURE - 1; - let start = dc_create_smeared_timestamps(&t.ctx, count as usize); - let next = dc_smeared_time(&t.ctx); + let start = dc_create_smeared_timestamps(&t.ctx, count as usize).await; + let next = dc_smeared_time(&t.ctx).await; assert!((start + count - 1) < next); let count = MAX_SECONDS_TO_LEND_FROM_FUTURE + 30; - let start = dc_create_smeared_timestamps(&t.ctx, count as usize); - let next = dc_smeared_time(&t.ctx); + let start = dc_create_smeared_timestamps(&t.ctx, count as usize).await; + let next = dc_smeared_time(&t.ctx).await; assert!((start + count - 1) < next); } } diff --git a/src/e2ee.rs b/src/e2ee.rs index 317d1a542..549ee4b72 100644 --- a/src/e2ee.rs +++ b/src/e2ee.rs @@ -137,7 +137,7 @@ pub async fn try_decrypt( let autocryptheader = Aheader::from_headers(context, &from, &mail.headers); if message_time > 0 { - peerstate = Peerstate::from_addr(context, &context.sql, &from); + peerstate = Peerstate::from_addr(context, &context.sql, &from).await; if let Some(ref mut peerstate) = peerstate { if let Some(ref header) = autocryptheader { @@ -167,7 +167,7 @@ pub async fn try_decrypt( .await { if peerstate.as_ref().map(|p| p.last_seen).unwrap_or_else(|| 0) == 0 { - peerstate = Peerstate::from_addr(&context, &context.sql, &from); + peerstate = Peerstate::from_addr(&context, &context.sql, &from).await; } if let Some(ref peerstate) = peerstate { if peerstate.degrade_event.is_some() { @@ -207,7 +207,7 @@ async fn load_or_generate_self_public_key( return SignedPublicKey::try_from(key) .map_err(|_| Error::Message("Not a public key".into())); } - let _guard = context.generating_key_mutex.lock().unwrap(); + let _guard = context.generating_key_mutex.lock().await; // Check again in case the key was generated while we were waiting for the lock. if let Some(key) = Key::from_self_public(context, &self_addr, &context.sql).await { @@ -377,15 +377,15 @@ mod tests { #[async_std::test] async fn test_prexisting() { - let t = dummy_context(); + let t = dummy_context().await; let test_addr = configure_alice_keypair(&t.ctx).await; assert_eq!(ensure_secret_key_exists(&t.ctx).await.unwrap(), test_addr); } - #[test] - fn test_not_configured() { - let t = dummy_context(); - assert!(ensure_secret_key_exists(&t.ctx).is_err()); + #[async_std::test] + async fn test_not_configured() { + let t = dummy_context().await; + assert!(ensure_secret_key_exists(&t.ctx).await.is_err()); } } @@ -418,40 +418,42 @@ Sent with my Delta Chat Messenger: https://delta.chat"; #[async_std::test] async fn test_existing() { - let t = dummy_context(); + let t = dummy_context().await; let addr = configure_alice_keypair(&t.ctx).await; let key = load_or_generate_self_public_key(&t.ctx, addr).await; assert!(key.is_ok()); } - #[test] + #[async_std::test] #[ignore] // generating keys is expensive - fn test_generate() { - let t = dummy_context(); + async fn test_generate() { + let t = dummy_context().await; let addr = "alice@example.org"; - let key0 = load_or_generate_self_public_key(&t.ctx, addr); + let key0 = load_or_generate_self_public_key(&t.ctx, addr).await; assert!(key0.is_ok()); - let key1 = load_or_generate_self_public_key(&t.ctx, addr); + let key1 = load_or_generate_self_public_key(&t.ctx, addr).await; assert!(key1.is_ok()); assert_eq!(key0.unwrap(), key1.unwrap()); } - #[test] + #[async_std::test] #[ignore] - fn test_generate_concurrent() { + async fn test_generate_concurrent() { use std::sync::Arc; - use std::thread; - let t = dummy_context(); + let t = dummy_context().await; let ctx = Arc::new(t.ctx); let ctx0 = Arc::clone(&ctx); - let thr0 = - thread::spawn(move || load_or_generate_self_public_key(&ctx0, "alice@example.org")); + let thr0 = async_std::task::spawn(async move { + load_or_generate_self_public_key(&ctx0, "alice@example.org").await + }); let ctx1 = Arc::clone(&ctx); - let thr1 = - thread::spawn(move || load_or_generate_self_public_key(&ctx1, "alice@example.org")); - let res0 = thr0.join().unwrap(); - let res1 = thr1.join().unwrap(); + let thr1 = async_std::task::spawn(async move { + load_or_generate_self_public_key(&ctx1, "alice@example.org").await + }); + + let res0 = thr0.await; + let res1 = thr1.await; assert_eq!(res0.unwrap(), res1.unwrap()); } } diff --git a/src/imap/mod.rs b/src/imap/mod.rs index 9cb7018f8..cc802318a 100644 --- a/src/imap/mod.rs +++ b/src/imap/mod.rs @@ -280,7 +280,9 @@ impl Imap { if (server_flags & DC_LP_AUTH_OAUTH2) != 0 { let addr: &str = config.addr.as_ref(); - if let Some(token) = dc_get_oauth2_access_token(context, addr, imap_pw, true) { + 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, @@ -298,11 +300,13 @@ impl Imap { let config = self.config.read().await; let imap_server: &str = config.imap_server.as_ref(); let imap_port = config.imap_port; - context.stock_string_repl_str2( - StockMessage::ServerResponse, - format!("{}:{}", imap_server, imap_port), - err.to_string(), - ) + context + .stock_string_repl_str2( + StockMessage::ServerResponse, + format!("{}:{}", imap_server, imap_port), + err.to_string(), + ) + .await }; // IMAP connection failures are reported to users emit_event!(context, Event::ErrorNetwork(message)); @@ -319,7 +323,9 @@ impl Imap { } Err((err, _)) => { let imap_user = self.config.read().await.imap_user.to_owned(); - let message = context.stock_string_repl_str(StockMessage::CannotLogin, &imap_user); + let message = context + .stock_string_repl_str(StockMessage::CannotLogin, &imap_user) + .await; emit_event!( context, @@ -539,7 +545,8 @@ impl Imap { // id we do not do this here, we'll miss the first message // as we will get in here again and fetch from lastseenuid+1 then - self.set_config_last_seen_uid(context, &folder, new_uid_validity, 0); + self.set_config_last_seen_uid(context, &folder, new_uid_validity, 0) + .await; return Ok((new_uid_validity, 0)); } @@ -577,7 +584,8 @@ impl Imap { } }; - self.set_config_last_seen_uid(context, &folder, new_uid_validity, new_last_seen_uid); + self.set_config_last_seen_uid(context, &folder, new_uid_validity, new_last_seen_uid) + .await; info!( context, "uid/validity change: new {}/{} current {}/{}", @@ -651,6 +659,7 @@ impl Imap { ); } else { let show = prefetch_should_download(context, &headers, show_emails) + .await .map_err(|err| { warn!(context, "prefetch_should_download error: {}", err); err @@ -687,7 +696,8 @@ impl Imap { }; if new_last_seen_uid > last_seen_uid { - self.set_config_last_seen_uid(context, &folder, uid_validity, new_last_seen_uid); + self.set_config_last_seen_uid(context, &folder, uid_validity, new_last_seen_uid) + .await; } if read_errors > 0 { @@ -1322,7 +1332,7 @@ async fn precheck_imf( server_uid: u32, ) -> bool { if let Ok((old_server_folder, old_server_uid, msg_id)) = - message::rfc724_mid_exists(context, &rfc724_mid) + message::rfc724_mid_exists(context, &rfc724_mid).await { if old_server_folder.is_empty() && old_server_uid == 0 { info!(context, "[move] detected bcc-self {}", rfc724_mid,); @@ -1342,7 +1352,7 @@ async fn precheck_imf( } if old_server_folder != server_folder || old_server_uid != server_uid { - update_server_uid(context, &rfc724_mid, server_folder, server_uid); + update_server_uid(context, &rfc724_mid, server_folder, server_uid).await; } true } else { @@ -1367,18 +1377,18 @@ fn prefetch_get_message_id(headers: &[mailparse::MailHeader]) -> Result } } -fn prefetch_is_reply_to_chat_message( +async fn prefetch_is_reply_to_chat_message( context: &Context, - headers: &[mailparse::MailHeader], + headers: &[mailparse::MailHeader<'_>], ) -> Result { if let Some(value) = headers.get_header_value(HeaderDef::InReplyTo)? { - if is_msgrmsg_rfc724_mid_in_list(context, &value) { + if is_msgrmsg_rfc724_mid_in_list(context, &value).await { return Ok(true); } } if let Some(value) = headers.get_header_value(HeaderDef::References)? { - if is_msgrmsg_rfc724_mid_in_list(context, &value) { + if is_msgrmsg_rfc724_mid_in_list(context, &value).await { return Ok(true); } } @@ -1386,13 +1396,13 @@ fn prefetch_is_reply_to_chat_message( Ok(false) } -fn prefetch_should_download( +async fn prefetch_should_download( context: &Context, - headers: &[mailparse::MailHeader], + headers: &[mailparse::MailHeader<'_>], show_emails: ShowEmails, ) -> Result { let is_chat_message = headers.get_header_value(HeaderDef::ChatVersion)?.is_some(); - let is_reply_to_chat_message = prefetch_is_reply_to_chat_message(context, &headers)?; + let is_reply_to_chat_message = prefetch_is_reply_to_chat_message(context, &headers).await?; // Autocrypt Setup Message should be shown even if it is from non-chat client. let is_autocrypt_setup_message = headers @@ -1403,7 +1413,8 @@ fn prefetch_should_download( .get_header_value(HeaderDef::From_)? .unwrap_or_default(); - let (_contact_id, blocked_contact, origin) = from_field_to_contact_id(context, &from_field)?; + let (_contact_id, blocked_contact, origin) = + from_field_to_contact_id(context, &from_field).await?; let accepted_contact = origin.is_known(); let show = is_autocrypt_setup_message diff --git a/src/imap/session.rs b/src/imap/session.rs index 8fb146874..9cac58515 100644 --- a/src/imap/session.rs +++ b/src/imap/session.rs @@ -29,15 +29,10 @@ impl Session { mailbox_pattern: Option<&str>, ) -> ImapResult> + '_ + Send + Unpin> { match self { - Session::Secure(i) => { - i.list(reference_name, mailbox_pattern).await - // list.collect::>().await? - } - Session::Insecure(i) => { + Session::Secure(i) => i.list(reference_name, mailbox_pattern).await, + Session::Insecure(_i) => { unimplemented!() // i.list(reference_name, mailbox_pattern).await - // .collect::>() - // .await? } } } @@ -86,7 +81,7 @@ impl Session { { let res = match self { Session::Secure(i) => i.fetch(sequence_set, query).await?, - Session::Insecure(i) => { + Session::Insecure(_i) => { unimplemented!() // i.fetch(sequence_set, query).await? } @@ -105,7 +100,7 @@ impl Session { { let res = match self { Session::Secure(i) => i.uid_fetch(uid_set, query).await?, - Session::Insecure(i) => { + Session::Insecure(_i) => { unimplemented!() // i.uid_fetch(uid_set, query).await? } @@ -125,7 +120,7 @@ impl Session { { let res = match self { Session::Secure(i) => i.uid_store(uid_set, query).await?, - Session::Insecure(i) => { + Session::Insecure(_i) => { unimplemented!() // i.uid_store(uid_set, query).await? } diff --git a/src/imex.rs b/src/imex.rs index 0084038bb..3ca1a2a89 100644 --- a/src/imex.rs +++ b/src/imex.rs @@ -115,9 +115,9 @@ pub async fn has_backup(context: &Context, dir_name: impl AsRef) -> Result } pub async fn initiate_key_transfer(context: &Context) -> Result { - ensure!(context.alloc_ongoing(), "could not allocate ongoing"); + ensure!(context.alloc_ongoing().await, "could not allocate ongoing"); let res = do_initiate_key_transfer(context).await; - context.free_ongoing(); + context.free_ongoing().await; res } @@ -125,10 +125,10 @@ async fn do_initiate_key_transfer(context: &Context) -> Result { let mut msg: Message; let setup_code = create_setup_code(context); /* this may require a keypair to be created. this may take a second ... */ - ensure!(!context.shall_stop_ongoing(), "canceled"); + ensure!(!context.shall_stop_ongoing().await, "canceled"); let setup_file_content = render_setup_file(context, &setup_code).await?; /* encrypting may also take a while ... */ - ensure!(!context.shall_stop_ongoing(), "canceled"); + ensure!(!context.shall_stop_ongoing().await, "canceled"); let setup_file_blob = BlobObject::create( context, "autocrypt-setup-message.html", @@ -148,11 +148,11 @@ async fn do_initiate_key_transfer(context: &Context) -> Result { ForcePlaintext::NoAutocryptHeader as i32, ); - ensure!(!context.shall_stop_ongoing(), "canceled"); + ensure!(!context.shall_stop_ongoing().await, "canceled"); let msg_id = chat::send_msg(context, chat_id, &mut msg).await?; info!(context, "Wait for setup message being sent ...",); - while !context.shall_stop_ongoing() { - std::thread::sleep(std::time::Duration::from_secs(1)); + while !context.shall_stop_ongoing().await { + async_std::task::sleep(std::time::Duration::from_secs(1)).await; if let Ok(msg) = Message::load_from_db(context, msg_id).await { if msg.is_sent() { info!(context, "... setup message sent.",); @@ -197,8 +197,8 @@ pub async fn render_setup_file(context: &Context, passphrase: &str) -> Result"); Ok(format!( concat!( @@ -368,7 +368,7 @@ pub fn normalize_setup_code(s: &str) -> String { } pub async fn job_imex_imap(context: &Context, job: &Job) -> Result<()> { - ensure!(context.alloc_ongoing(), "could not allocate ongoing"); + ensure!(context.alloc_ongoing().await, "could not allocate ongoing"); let what: Option = job.param.get_int(Param::Cmd).and_then(ImexMode::from_i32); let param = job.param.get(Param::Arg).unwrap_or_default(); @@ -380,7 +380,7 @@ pub async fn job_imex_imap(context: &Context, job: &Job) -> Result<()> { if what == Some(ImexMode::ExportBackup) || what == Some(ImexMode::ExportSelfKeys) { // before we export anything, make sure the private key exists if e2ee::ensure_secret_key_exists(context).await.is_err() { - context.free_ongoing(); + context.free_ongoing().await; bail!("Cannot create private key or private key not available."); } else { dc_create_folder(context, ¶m)?; @@ -396,7 +396,7 @@ pub async fn job_imex_imap(context: &Context, job: &Job) -> Result<()> { bail!("unknown IMEX type"); } }; - context.free_ongoing(); + context.free_ongoing().await; match success { Ok(()) => { info!(context, "IMEX successfully completed"); @@ -423,7 +423,7 @@ async fn import_backup(context: &Context, backup_to_import: impl AsRef) -> !context.is_configured().await, "Cannot import backups to accounts in use." ); - context.sql.close(&context); + context.sql.close(&context).await; dc_delete_file(context, context.get_dbfile()); ensure!( !context.get_dbfile().exists(), @@ -448,7 +448,7 @@ async fn import_backup(context: &Context, backup_to_import: impl AsRef) -> let total_files_cnt = context .sql - .query_get_value::<_, isize>(context, "SELECT COUNT(*) FROM backup_blobs;", params![]) + .query_get_value::(context, "SELECT COUNT(*) FROM backup_blobs;", paramsv![]) .await .unwrap_or_default() as usize; info!( @@ -456,11 +456,11 @@ async fn import_backup(context: &Context, backup_to_import: impl AsRef) -> "***IMPORT-in-progress: total_files_cnt={:?}", total_files_cnt, ); - let res = context + let files = context .sql .query_map( "SELECT file_name, file_content FROM backup_blobs ORDER BY id;", - params![], + paramsv![], |row| { let name: String = row.get(0)?; let blob: Vec = row.get(1)?; @@ -468,46 +468,45 @@ async fn import_backup(context: &Context, backup_to_import: impl AsRef) -> Ok((name, blob)) }, |files| { - for (processed_files_cnt, file) in files.enumerate() { - let (file_name, file_blob) = file?; - if context.shall_stop_ongoing() { - return Ok(false); - } - let mut permille = processed_files_cnt * 1000 / total_files_cnt; - if permille < 10 { - permille = 10 - } - if permille > 990 { - permille = 990 - } - context.call_cb(Event::ImexProgress(permille)); - if file_blob.is_empty() { - continue; - } - - let path_filename = context.get_blobdir().join(file_name); - dc_write_file(context, &path_filename, &file_blob)?; - } - Ok(true) + files + .collect::, _>>() + .map_err(Into::into) }, ) - .await; + .await?; - match res { - Ok(all_files_extracted) => { - if all_files_extracted { - // only delete backup_blobs if all files were successfully extracted - context - .sql - .execute("DROP TABLE backup_blobs;", params![]) - .await?; - context.sql.execute("VACUUM;", params![]).await.ok(); - Ok(()) - } else { - bail!("received stop signal"); - } + let mut all_files_extracted = true; + for (processed_files_cnt, (file_name, file_blob)) in files.into_iter().enumerate() { + if context.shall_stop_ongoing().await { + all_files_extracted = false; + break; } - Err(err) => Err(err.into()), + let mut permille = processed_files_cnt * 1000 / total_files_cnt; + if permille < 10 { + permille = 10 + } + if permille > 990 { + permille = 990 + } + context.call_cb(Event::ImexProgress(permille)); + if file_blob.is_empty() { + continue; + } + + let path_filename = context.get_blobdir().join(file_name); + dc_write_file(context, &path_filename, &file_blob)?; + } + + if all_files_extracted { + // only delete backup_blobs if all files were successfully extracted + context + .sql + .execute("DROP TABLE backup_blobs;", paramsv![]) + .await?; + context.sql.execute("VACUUM;", paramsv![]).await.ok(); + Ok(()) + } else { + bail!("received stop signal"); } } @@ -526,10 +525,10 @@ async fn export_backup(context: &Context, dir: impl AsRef) -> Result<()> { sql::housekeeping(context).await; - context.sql.execute("VACUUM;", params![]).await.ok(); + context.sql.execute("VACUUM;", paramsv![]).await.ok(); // we close the database during the copy of the dbfile - context.sql.close(context); + context.sql.close(context).await; info!( context, "Backup '{}' to '{}'.", @@ -537,7 +536,10 @@ async fn export_backup(context: &Context, dir: impl AsRef) -> Result<()> { dest_path_filename.display(), ); let copied = dc_copy_file(context, context.get_dbfile(), &dest_path_filename); - context.sql.open(&context, &context.get_dbfile(), false); + context + .sql + .open(&context, &context.get_dbfile(), false) + .await; if !copied { bail!( @@ -566,7 +568,7 @@ async fn export_backup(context: &Context, dir: impl AsRef) -> Result<()> { Ok(()) } }; - dest_sql.close(context); + dest_sql.close(context).await; Ok(res?) } @@ -577,7 +579,7 @@ async fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> { if !sql.table_exists("backup_blobs").await? { sql.execute( "CREATE TABLE backup_blobs (id INTEGER PRIMARY KEY, file_name, file_content);", - params![], + paramsv![], ) .await?; } @@ -589,17 +591,14 @@ async fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> { info!(context, "EXPORT: total_files_cnt={}", total_files_cnt); - sql.with_conn(|conn| { + sql.with_conn_async(|conn| async move { // scan directory, pass 2: copy files let dir_handle = std::fs::read_dir(&dir)?; - let mut stmt = conn - .prepare_cached("INSERT INTO backup_blobs (file_name, file_content) VALUES (?, ?);")?; - let mut processed_files_cnt = 0; for entry in dir_handle { let entry = entry?; - if context.shall_stop_ongoing() { + if context.shall_stop_ongoing().await { return Ok(()); } processed_files_cnt += 1; @@ -618,7 +617,10 @@ async fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> { continue; } // bail out if we can't insert - stmt.execute(params![name, buf])?; + let mut stmt = conn.prepare_cached( + "INSERT INTO backup_blobs (file_name, file_content) VALUES (?, ?);", + )?; + stmt.execute(paramsv![name, buf])?; } } Ok(()) @@ -690,7 +692,7 @@ async fn export_self_keys(context: &Context, dir: impl AsRef) -> Result<() .sql .query_map( "SELECT id, public_key, private_key, is_default FROM keypairs;", - params![], + paramsv![], |row| { let id = row.get(0)?; let public_key_blob: Vec = row.get(1)?; @@ -766,7 +768,7 @@ mod tests { #[async_std::test] async fn test_render_setup_file() { - let t = test_context(Some(Box::new(logging_cb))); + let t = test_context(Some(Box::new(logging_cb))).await; configure_alice_keypair(&t.ctx).await; let msg = render_setup_file(&t.ctx, "hello").await.unwrap(); @@ -786,9 +788,10 @@ mod tests { #[async_std::test] async fn test_render_setup_file_newline_replace() { - let t = dummy_context(); + let t = dummy_context().await; t.ctx .set_stock_translation(StockMessage::AcSetupMsgBody, "hello\r\nthere".to_string()) + .await .unwrap(); configure_alice_keypair(&t.ctx).await; let msg = render_setup_file(&t.ctx, "pw").await.unwrap(); @@ -796,9 +799,9 @@ mod tests { assert!(msg.contains("

hello
there

")); } - #[test] - fn test_create_setup_code() { - let t = dummy_context(); + #[async_std::test] + async fn test_create_setup_code() { + let t = dummy_context().await; let setupcode = create_setup_code(&t.ctx); assert_eq!(setupcode.len(), 44); assert_eq!(setupcode.chars().nth(4).unwrap(), '-'); @@ -811,9 +814,9 @@ mod tests { assert_eq!(setupcode.chars().nth(39).unwrap(), '-'); } - #[test] - fn test_export_key_to_asc_file() { - let context = dummy_context(); + #[async_std::test] + async fn test_export_key_to_asc_file() { + let context = dummy_context().await; let key = Key::from(alice_keypair().public); let blobdir = "$BLOBDIR"; assert!(export_key_to_asc_file(&context.ctx, blobdir, None, &key).is_ok()); @@ -839,9 +842,9 @@ mod tests { const S_EM_SETUPCODE: &str = "1742-0185-6197-1303-7016-8412-3581-4441-0597"; const S_EM_SETUPFILE: &str = include_str!("../test-data/message/stress.txt"); - #[test] - fn test_split_and_decrypt() { - let ctx = dummy_context(); + #[async_std::test] + async fn test_split_and_decrypt() { + let ctx = dummy_context().await; let context = &ctx.ctx; let buf_1 = S_EM_SETUPFILE.as_bytes().to_vec(); diff --git a/src/job.rs b/src/job.rs index 19ebc75e0..1274ce946 100644 --- a/src/job.rs +++ b/src/job.rs @@ -149,7 +149,7 @@ impl Job { async fn delete(&self, context: &Context) -> bool { context .sql - .execute("DELETE FROM jobs WHERE id=?;", params![self.job_id as i32]) + .execute("DELETE FROM jobs WHERE id=?;", paramsv![self.job_id as i32]) .await .is_ok() } @@ -162,7 +162,7 @@ impl Job { .sql .execute( "UPDATE jobs SET desired_timestamp=?, tries=?, param=? WHERE id=?;", - params![ + paramsv![ self.desired_timestamp, self.tries as i64, self.param.to_string(), @@ -283,7 +283,7 @@ impl Job { /* if there is a msg-id and it does not exist in the db, cancel sending. this happends if dc_delete_msgs() was called before the generated mime was sent out */ - if 0 != self.foreign_id && !message::exists(context, MsgId::new(self.foreign_id)) { + if 0 != self.foreign_id && !message::exists(context, MsgId::new(self.foreign_id)).await { return Status::Finished(Err(format_err!( "Not sending Message {} as it was deleted", self.foreign_id @@ -295,7 +295,7 @@ impl Job { async move { // smtp success, update db ASAP, then delete smtp file if 0 != foreign_id { - set_delivered(context, MsgId::new(foreign_id)); + set_delivered(context, MsgId::new(foreign_id)).await; } // now also delete the generated file dc_delete_file(context, filename); @@ -316,7 +316,7 @@ impl Job { .sql .query_map( "SELECT id, param FROM jobs WHERE foreign_id=? AND id!=?", - params![contact_id, self.job_id], + paramsv![contact_id, self.job_id], |row| { let job_id: u32 = row.get(0)?; let params_str: String = row.get(1)?; @@ -385,8 +385,9 @@ impl Job { } let msg = job_try!(Message::load_from_db(context, msg_id).await); - let mimefactory = job_try!(MimeFactory::from_mdn(context, &msg, additional_rfc724_mids)); - let rendered_msg = job_try!(mimefactory.render()); + let mimefactory = + job_try!(MimeFactory::from_mdn(context, &msg, additional_rfc724_mids).await); + let rendered_msg = job_try!(mimefactory.render().await); let body = rendered_msg.message; let addr = contact.get_addr(); @@ -443,7 +444,8 @@ impl Job { { ImapActionResult::RetryLater => Status::RetryLater, ImapActionResult::Success => { - message::update_server_uid(context, &msg.rfc724_mid, &dest_folder, dest_uid); + message::update_server_uid(context, &msg.rfc724_mid, &dest_folder, dest_uid) + .await; Status::Finished(Ok(())) } ImapActionResult::Failed => { @@ -462,7 +464,7 @@ impl Job { let mut msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)).await); if !msg.rfc724_mid.is_empty() { - if message::rfc724_mid_cnt(context, &msg.rfc724_mid) > 1 { + if message::rfc724_mid_cnt(context, &msg.rfc724_mid).await > 1 { info!( context, "The message is deleted from the server when all parts are deleted.", @@ -480,7 +482,7 @@ impl Job { return Status::RetryNow; } } - Message::delete_from_db(context, msg.id); + Message::delete_from_db(context, msg.id).await; Status::Finished(Ok(())) } else { /* eg. device messages have no Message-ID */ @@ -576,7 +578,7 @@ impl Job { pub async fn kill_action(context: &Context, action: Action) -> bool { context .sql - .execute("DELETE FROM jobs WHERE action=?;", params![action]) + .execute("DELETE FROM jobs WHERE action=?;", paramsv![action]) .await .is_ok() } @@ -590,7 +592,7 @@ pub async fn kill_ids(context: &Context, job_ids: &[u32]) -> sql::Result<()> { "DELETE FROM jobs WHERE id IN({})", job_ids.iter().map(|_| "?").join(",") ), - job_ids, + job_ids.iter().map(|i| i as &dyn crate::ToSql).collect(), ) .await?; Ok(()) @@ -715,7 +717,7 @@ async fn get_next_wakeup_time(context: &Context, thread: Thread) -> time::Durati .query_get_value( context, "SELECT MIN(desired_timestamp) FROM jobs WHERE thread=?;", - params![thread], + paramsv![thread], ) .await .unwrap_or_default(); @@ -750,19 +752,19 @@ pub async fn maybe_network(context: &Context) { pub async fn action_exists(context: &Context, action: Action) -> bool { context .sql - .exists("SELECT id FROM jobs WHERE action=?;", params![action]) + .exists("SELECT id FROM jobs WHERE action=?;", paramsv![action]) .await .unwrap_or_default() } async fn set_delivered(context: &Context, msg_id: MsgId) { - message::update_msg_state(context, msg_id, MessageState::OutDelivered); + message::update_msg_state(context, msg_id, MessageState::OutDelivered).await; let chat_id: ChatId = context .sql .query_get_value( context, "SELECT chat_id FROM msgs WHERE id=?", - params![msg_id], + paramsv![msg_id], ) .await .unwrap_or_default(); @@ -772,7 +774,7 @@ async fn set_delivered(context: &Context, msg_id: MsgId) { // special case for DC_JOB_SEND_MSG_TO_SMTP pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> { let mut msg = Message::load_from_db(context, msg_id).await?; - msg.try_calc_and_set_dimensions(context).ok(); + msg.try_calc_and_set_dimensions(context).await.ok(); /* create message */ let needs_encryption = msg.param.get_bool(Param::GuaranteeE2ee).unwrap_or_default(); @@ -785,7 +787,7 @@ pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> { } }; - let mimefactory = MimeFactory::from_msg(context, &msg, attach_selfavatar)?; + let mimefactory = MimeFactory::from_msg(context, &msg, attach_selfavatar).await?; let mut recipients = mimefactory.recipients(); @@ -808,14 +810,17 @@ pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> { context, "message {} has no recipient, skipping smtp-send", msg_id ); - set_delivered(context, msg_id); + set_delivered(context, msg_id).await; return Ok(()); } - let rendered_msg = mimefactory.render().map_err(|err| { - message::set_msg_failed(context, msg_id, Some(err.to_string())); - err - })?; + let rendered_msg = match mimefactory.render().await { + Ok(res) => Ok(res), + Err(err) => { + message::set_msg_failed(context, msg_id, Some(err.to_string())).await; + Err(err) + } + }?; if needs_encryption && !rendered_msg.is_encrypted { /* unrecoverable */ @@ -823,7 +828,8 @@ pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> { context, msg_id, Some("End-to-end-encryption unavailable unexpectedly."), - ); + ) + .await; bail!( "e2e encryption unavailable {} - {:?}", msg_id, @@ -857,7 +863,7 @@ pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> { if rendered_msg.is_encrypted && !needs_encryption { msg.param.set_int(Param::GuaranteeE2ee, 1); - msg.save_param_to_disk(context); + msg.save_param_to_disk(context).await; } add_smtp_job( @@ -917,77 +923,78 @@ async fn job_perform(context: &Context, thread: Thread, probe_network: bool) { x => x, }; - // if Action::ConfigureImap == job.action || Action::ImexImap == job.action { - // context.sentbox_thread.unsuspend(context).await; - // context.mvbox_thread.unsuspend(context).await; - // suspend_smtp_thread(context, false).await; - // break; - // } + if Action::ConfigureImap == job.action || Action::ImexImap == job.action { + context.sentbox_thread.unsuspend(context).await; + context.mvbox_thread.unsuspend(context).await; + suspend_smtp_thread(context, false).await; + break; + } - // match try_res { - // Status::RetryNow | Status::RetryLater => { - // let tries = job.tries + 1; + match try_res { + Status::RetryNow | Status::RetryLater => { + let tries = job.tries + 1; - // if tries < JOB_RETRIES { - // info!( - // context, - // "{} thread increases job {} tries to {}", thread, job, tries - // ); - // job.tries = tries; - // let time_offset = get_backoff_time_offset(tries); - // job.desired_timestamp = time() + time_offset; - // job.update(context).await; - // info!( - // context, - // "{}-job #{} not succeeded on try #{}, retry in {} seconds.", - // thread, - // job.job_id as u32, - // tries, - // time_offset - // ); - // if thread == Thread::Smtp && tries < JOB_RETRIES - 1 { - // context.smtp.state.write().await.perform_jobs_needed = - // PerformJobsNeeded::AvoidDos; - // } - // } else { - // info!( - // context, - // "{} thread removes job {} as it exhausted {} retries", - // thread, - // job, - // JOB_RETRIES - // ); - // if job.action == Action::SendMsgToSmtp { - // message::set_msg_failed( - // context, - // MsgId::new(job.foreign_id), - // job.pending_error.as_ref(), - // ); - // } - // job.delete(context).await; - // } - // if !probe_network { - // continue; - // } - // // on dc_maybe_network() we stop trying here; - // // these jobs are already tried once. - // // otherwise, we just continue with the next job - // // to give other jobs a chance being tried at least once. - // break; - // } - // Status::Finished(res) => { - // if let Err(err) = res { - // warn!( - // context, - // "{} removes job {} as it failed with error {:?}", thread, job, err - // ); - // } else { - // info!(context, "{} removes job {} as it succeeded", thread, job); - // } + if tries < JOB_RETRIES { + info!( + context, + "{} thread increases job {} tries to {}", thread, job, tries + ); + job.tries = tries; + let time_offset = get_backoff_time_offset(tries); + job.desired_timestamp = time() + time_offset; + job.update(context).await; + info!( + context, + "{}-job #{} not succeeded on try #{}, retry in {} seconds.", + thread, + job.job_id as u32, + tries, + time_offset + ); + if thread == Thread::Smtp && tries < JOB_RETRIES - 1 { + context.smtp.state.write().await.perform_jobs_needed = + PerformJobsNeeded::AvoidDos; + } + } else { + info!( + context, + "{} thread removes job {} as it exhausted {} retries", + thread, + job, + JOB_RETRIES + ); + if job.action == Action::SendMsgToSmtp { + message::set_msg_failed( + context, + MsgId::new(job.foreign_id), + job.pending_error.as_ref(), + ) + .await; + } + job.delete(context).await; + } + if !probe_network { + continue; + } + // on dc_maybe_network() we stop trying here; + // these jobs are already tried once. + // otherwise, we just continue with the next job + // to give other jobs a chance being tried at least once. + break; + } + Status::Finished(res) => { + if let Err(err) = res { + warn!( + context, + "{} removes job {} as it failed with error {:?}", thread, job, err + ); + } else { + info!(context, "{} removes job {} as it succeeded", thread, job); + } - // job.delete(context).await; - // } - // } + job.delete(context).await; + } + } } } @@ -1024,7 +1031,7 @@ async fn perform_job_action( location::job_maybe_send_locations_ended(context, &mut job).await } Action::Housekeeping => { - sql::housekeeping(context); + sql::housekeeping(context).await; Status::Finished(Ok(())) } }; @@ -1108,7 +1115,7 @@ pub async fn add( context.sql.execute( "INSERT INTO jobs (added_timestamp, thread, action, foreign_id, param, desired_timestamp) VALUES (?,?,?,?,?,?);", - params![ + paramsv![ timestamp, thread, action, @@ -1154,9 +1161,11 @@ async fn load_next_job(context: &Context, thread: Thread, probe_network: bool) - FROM jobs WHERE thread=? AND tries>0 ORDER BY desired_timestamp, action DESC;" }; - let params_no_probe = params![thread as i64, time()]; - let params_probe = params![thread as i64]; - let params: &[&dyn rusqlite::ToSql] = if !probe_network { + let thread_i = thread as i64; + let t = time(); + let params_no_probe = paramsv![thread_i, t]; + let params_probe = paramsv![thread_i]; + let params = if !probe_network { params_no_probe } else { params_probe @@ -1201,7 +1210,7 @@ mod tests { use crate::test_utils::*; - fn insert_job(context: &Context, foreign_id: i64) { + async fn insert_job(context: &Context, foreign_id: i64) { let now = time(); context .sql @@ -1209,7 +1218,7 @@ mod tests { "INSERT INTO jobs (added_timestamp, thread, action, foreign_id, param, desired_timestamp) VALUES (?, ?, ?, ?, ?, ?);", - params![ + paramsv![ now, Thread::from(Action::MoveMsg), Action::MoveMsg, @@ -1218,21 +1227,22 @@ mod tests { now ], ) + .await .unwrap(); } - #[test] - fn test_load_next_job() { + #[async_std::test] + async fn test_load_next_job() { // We want to ensure that loading jobs skips over jobs which // fails to load from the database instead of failing to load // all jobs. - let t = dummy_context(); - insert_job(&t.ctx, -1); // This can not be loaded into Job struct. - let jobs = load_next_job(&t.ctx, Thread::from(Action::MoveMsg), false); + let t = dummy_context().await; + insert_job(&t.ctx, -1).await; // This can not be loaded into Job struct. + let jobs = load_next_job(&t.ctx, Thread::from(Action::MoveMsg), false).await; assert!(jobs.is_none()); - insert_job(&t.ctx, 1); - let jobs = load_next_job(&t.ctx, Thread::from(Action::MoveMsg), false); + insert_job(&t.ctx, 1).await; + let jobs = load_next_job(&t.ctx, Thread::from(Action::MoveMsg), false).await; assert!(jobs.is_some()); } } diff --git a/src/key.rs b/src/key.rs index 0429f3a58..0ab6bcc23 100644 --- a/src/key.rs +++ b/src/key.rs @@ -207,7 +207,7 @@ impl Key { sql.query_get_value( context, "SELECT public_key FROM keypairs WHERE addr=? AND is_default=1;", - &[addr], + paramsv![addr], ) .await .and_then(|blob: Vec| Self::from_slice(&blob, KeyType::Public)) @@ -221,7 +221,7 @@ impl Key { sql.query_get_value( context, "SELECT private_key FROM keypairs WHERE addr=? AND is_default=1;", - &[self_addr.as_ref()], + paramsv![self_addr.as_ref()], ) .await .and_then(|blob: Vec| Self::from_slice(&blob, KeyType::Private)) @@ -368,37 +368,37 @@ pub async fn store_self_keypair( .sql .execute( "DELETE FROM keypairs WHERE public_key=? OR private_key=?;", - params![public_key, secret_key], + paramsv![public_key, secret_key], ) .await .map_err(|err| SaveKeyError::new("failed to remove old use of key", err))?; if default == KeyPairUse::Default { context .sql - .execute("UPDATE keypairs SET is_default=0;", params![]) + .execute("UPDATE keypairs SET is_default=0;", paramsv![]) .await .map_err(|err| SaveKeyError::new("failed to clear default", err))?; } let is_default = match default { - KeyPairUse::Default => true, - KeyPairUse::ReadOnly => false, + KeyPairUse::Default => true as i32, + KeyPairUse::ReadOnly => false as i32, }; + + let addr = keypair.addr.to_string(); + let t = time(); + + let params = paramsv![addr, is_default, public_key, secret_key, t]; context .sql .execute( "INSERT INTO keypairs (addr, is_default, public_key, private_key, created) VALUES (?,?,?,?,?);", - params![ - keypair.addr.to_string(), - is_default as i32, - public_key, - secret_key, - time() - ], + params, ) .await - .map(|_| ()) - .map_err(|err| SaveKeyError::new("failed to insert keypair", err)) + .map_err(|err| SaveKeyError::new("failed to insert keypair", err))?; + + Ok(()) } /// Make a fingerprint human-readable, in hex format. @@ -433,6 +433,7 @@ mod tests { use crate::test_utils::*; use std::convert::TryFrom; + use async_std::sync::Arc; use lazy_static::lazy_static; lazy_static! { @@ -583,22 +584,29 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD assert_eq!(public.primary_key, KEYPAIR.public.primary_key); } - #[test] - fn test_save_self_key_twice() { + #[async_std::test] + async fn test_save_self_key_twice() { // Saving the same key twice should result in only one row in // the keypairs table. - let t = dummy_context(); - let nrows = || { - t.ctx - .sql - .query_get_value::<_, u32>(&t.ctx, "SELECT COUNT(*) FROM keypairs;", params![]) + let t = dummy_context().await; + let ctx = Arc::new(t.ctx); + + let ctx1 = ctx.clone(); + let nrows = || async { + ctx1.sql + .query_get_value::(&ctx1, "SELECT COUNT(*) FROM keypairs;", paramsv![]) + .await .unwrap() }; - assert_eq!(nrows(), 0); - store_self_keypair(&t.ctx, &KEYPAIR, KeyPairUse::Default).unwrap(); - assert_eq!(nrows(), 1); - store_self_keypair(&t.ctx, &KEYPAIR, KeyPairUse::Default).unwrap(); - assert_eq!(nrows(), 1); + assert_eq!(nrows().await, 0); + store_self_keypair(&ctx, &KEYPAIR, KeyPairUse::Default) + .await + .unwrap(); + assert_eq!(nrows().await, 1); + store_self_keypair(&ctx, &KEYPAIR, KeyPairUse::Default) + .await + .unwrap(); + assert_eq!(nrows().await, 1); } // Convenient way to create a new key if you need one, run with diff --git a/src/keyring.rs b/src/keyring.rs index 4009a0591..f1da55e13 100644 --- a/src/keyring.rs +++ b/src/keyring.rs @@ -36,7 +36,7 @@ impl<'a> Keyring<'a> { sql.query_get_value( context, "SELECT private_key FROM keypairs ORDER BY addr=? DESC, is_default DESC;", - &[self_addr.as_ref()], + paramsv![self_addr.as_ref().to_string()], ) .await .and_then(|blob: Vec| Key::from_slice(&blob, KeyType::Private)) diff --git a/src/lib.rs b/src/lib.rs index 3bd459ad9..c9f8aafe9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,19 @@ extern crate strum_macros; #[macro_use] extern crate debug_stub_derive; +pub trait ToSql: rusqlite::ToSql + Send + Sync {} + +impl ToSql for T {} + +macro_rules! paramsv { + () => { + Vec::new() + }; + ($($param:expr),+ $(,)?) => { + vec![$(&$param as &dyn $crate::ToSql),+] + }; +} + #[macro_use] pub mod log; #[macro_use] @@ -58,7 +71,7 @@ pub mod qr; pub mod securejoin; mod simplify; mod smtp; -pub mod sql; +mod sql; pub mod stock; mod token; #[macro_use] diff --git a/src/location.rs b/src/location.rs index 1ca5cd3f4..728f8c654 100644 --- a/src/location.rs +++ b/src/location.rs @@ -202,7 +202,7 @@ pub async fn send_locations_to_chat(context: &Context, chat_id: ChatId, seconds: SET locations_send_begin=?, \ locations_send_until=? \ WHERE id=?", - params![ + paramsv![ if 0 != seconds { now } else { 0 }, if 0 != seconds { now + seconds } else { 0 }, chat_id, @@ -213,16 +213,20 @@ pub async fn send_locations_to_chat(context: &Context, chat_id: ChatId, seconds: { if 0 != seconds && !is_sending_locations_before { let mut msg = Message::new(Viewtype::Text); - msg.text = - Some(context.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0)); + msg.text = Some( + context + .stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0) + .await, + ); msg.param.set_cmd(SystemMessage::LocationStreamingEnabled); chat::send_msg(context, chat_id, &mut msg) .await .unwrap_or_default(); } else if 0 == seconds && is_sending_locations_before { - let stock_str = - context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0); - chat::add_info_msg(context, chat_id, stock_str); + let stock_str = context + .stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0) + .await; + chat::add_info_msg(context, chat_id, stock_str).await; } context.call_cb(Event::ChatModified(chat_id)); if 0 != seconds { @@ -258,7 +262,7 @@ pub async fn is_sending_locations_to_chat(context: &Context, chat_id: ChatId) -> .sql .exists( "SELECT id FROM chats WHERE (? OR id=?) AND locations_send_until>?;", - params![if chat_id.is_unset() { 1 } else { 0 }, chat_id, time()], + paramsv![if chat_id.is_unset() { 1 } else { 0 }, chat_id, time()], ) .await .unwrap_or_default() @@ -274,7 +278,7 @@ pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64 .sql .query_map( "SELECT id FROM chats WHERE locations_send_until>?;", - params![time()], + paramsv![time()], |row| row.get::<_, i32>(0), |chats| chats.collect::, _>>().map_err(Into::into), ) @@ -284,7 +288,7 @@ pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64 if let Err(err) = context.sql.execute( "INSERT INTO locations \ (latitude, longitude, accuracy, timestamp, chat_id, from_id) VALUES (?,?,?,?,?,?);", - params![ + paramsv![ latitude, longitude, accuracy, @@ -326,7 +330,7 @@ pub async fn get_range( AND (? OR l.from_id=?) \ AND (l.independent=1 OR (l.timestamp>=? AND l.timestamp<=?)) \ ORDER BY l.timestamp DESC, l.id DESC, msg_id DESC;", - params![ + paramsv![ if chat_id.is_unset() { 1 } else { 0 }, chat_id, if contact_id == 0 { 1 } else { 0 }, @@ -377,7 +381,7 @@ fn is_marker(txt: &str) -> bool { pub async fn delete_all(context: &Context) -> Result<(), Error> { context .sql - .execute("DELETE FROM locations;", params![]) + .execute("DELETE FROM locations;", paramsv![]) .await?; context.call_cb(Event::LocationChanged(None)); Ok(()) @@ -393,7 +397,7 @@ pub async fn get_kml(context: &Context, chat_id: ChatId) -> Result<(String, u32) let (locations_send_begin, locations_send_until, locations_last_sent) = context.sql.query_row( "SELECT locations_send_begin, locations_send_until, locations_last_sent FROM chats WHERE id=?;", - params![chat_id], |row| { + paramsv![chat_id], |row| { let send_begin: i64 = row.get(0)?; let send_until: i64 = row.get(1)?; let last_sent: i64 = row.get(2)?; @@ -419,7 +423,7 @@ pub async fn get_kml(context: &Context, chat_id: ChatId) -> Result<(String, u32) AND independent=0 \ GROUP BY timestamp \ ORDER BY timestamp;", - params![DC_CONTACT_ID_SELF, locations_send_begin, locations_last_sent, DC_CONTACT_ID_SELF], + paramsv![DC_CONTACT_ID_SELF, locations_send_begin, locations_last_sent, DC_CONTACT_ID_SELF], |row| { let location_id: i32 = row.get(0)?; let latitude: f64 = row.get(1)?; @@ -486,7 +490,7 @@ pub async fn set_kml_sent_timestamp( .sql .execute( "UPDATE chats SET locations_last_sent=? WHERE id=?;", - params![timestamp, chat_id], + paramsv![timestamp, chat_id], ) .await?; Ok(()) @@ -501,7 +505,7 @@ pub async fn set_msg_location_id( .sql .execute( "UPDATE msgs SET location_id=? WHERE id=?;", - params![location_id, msg_id], + paramsv![location_id, msg_id], ) .await?; @@ -532,10 +536,10 @@ pub async fn save( VALUES (?,?,?,?,?,?,?);", )?; - let exists = stmt_test.exists(params![location.timestamp, contact_id as i32])?; + let exists = stmt_test.exists(paramsv![location.timestamp, contact_id as i32])?; if independent || !exists { - stmt_insert.execute(params![ + stmt_insert.execute(paramsv![ location.timestamp, contact_id as i32, chat_id, @@ -582,7 +586,7 @@ pub(crate) async fn job_maybe_send_locations(context: &Context, _job: &Job) -> j "SELECT id, locations_send_begin, locations_last_sent \ FROM chats \ WHERE locations_send_until>?;", - params![now], + paramsv![now], |row| { let chat_id: ChatId = row.get(0)?; let locations_send_begin: i64 = row.get(1)?; @@ -621,10 +625,10 @@ pub(crate) async fn job_maybe_send_locations(context: &Context, _job: &Job) -> j .iter() .filter_map(|(chat_id, locations_send_begin, locations_last_sent)| { if !stmt_locations - .exists(params![ + .exists(paramsv![ DC_CONTACT_ID_SELF, - locations_send_begin, - locations_last_sent, + *locations_send_begin, + *locations_last_sent, ]) .unwrap_or_default() { @@ -682,7 +686,7 @@ pub(crate) async fn job_maybe_send_locations_ended( .sql .query_row( "SELECT locations_send_begin, locations_send_until FROM chats WHERE id=?", - params![chat_id], + paramsv![chat_id], |row| Ok((row.get::<_, i64>(0)?, row.get::<_, i64>(1)?)), ) .await @@ -696,11 +700,13 @@ pub(crate) async fn job_maybe_send_locations_ended( // not streaming, device-message already sent job_try!(context.sql.execute( "UPDATE chats SET locations_send_begin=0, locations_send_until=0 WHERE id=?", - params![chat_id], + paramsv![chat_id], ).await); - let stock_str = context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0); - chat::add_info_msg(context, chat_id, stock_str); + let stock_str = context + .stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0) + .await; + chat::add_info_msg(context, chat_id, stock_str).await; context.call_cb(Event::ChatModified(chat_id)); } } @@ -712,9 +718,9 @@ mod tests { use super::*; use crate::test_utils::dummy_context; - #[test] - fn test_kml_parse() { - let context = dummy_context(); + #[async_std::test] + async fn test_kml_parse() { + let context = dummy_context().await; let xml = b"\n\n\n2019-03-06T21:09:57Z9.423110,53.790302\n\n \n\t2018-12-13T22:11:12Z\t 19.423110 \t , \n 63.790302\n \n\n"; diff --git a/src/message.rs b/src/message.rs index 401f93be7..8ae5cd2ec 100644 --- a/src/message.rs +++ b/src/message.rs @@ -251,7 +251,7 @@ impl Message { " FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id", " WHERE m.id=?;" ), - params![id], + paramsv![id], |row| { let mut msg = Message::default(); // msg.id = row.get::<_, AnyMsgId>("id")?; @@ -310,12 +310,12 @@ impl Message { if let Ok(msg) = Message::load_from_db(context, msg_id).await { context .sql - .execute("DELETE FROM msgs WHERE id=?;", params![msg.id]) + .execute("DELETE FROM msgs WHERE id=?;", paramsv![msg.id]) .await .ok(); context .sql - .execute("DELETE FROM msgs_mdns WHERE msg_id=?;", params![msg.id]) + .execute("DELETE FROM msgs_mdns WHERE msg_id=?;", paramsv![msg.id]) .await .ok(); } @@ -339,7 +339,7 @@ impl Message { self.param.get_path(Param::File, context).unwrap_or(None) } - pub fn try_calc_and_set_dimensions(&mut self, context: &Context) -> Result<(), Error> { + pub async fn try_calc_and_set_dimensions(&mut self, context: &Context) -> Result<(), Error> { if chat::msgtype_has_file(self.viewtype) { let file_param = self.param.get_path(Param::File, context)?; if let Some(path_and_filename) = file_param { @@ -357,7 +357,7 @@ impl Message { } if !self.id.is_unset() { - self.save_param_to_disk(context); + self.save_param_to_disk(context).await; } } } @@ -495,12 +495,12 @@ impl Message { None }; - ret.fill(self, chat, contact.as_ref(), context); + ret.fill(self, chat, contact.as_ref(), context).await; ret } - pub fn get_summarytext(&self, context: &Context, approx_characters: usize) -> String { + pub async fn get_summarytext(&self, context: &Context, approx_characters: usize) -> String { get_summarytext_by_raw( self.viewtype, self.text.as_ref(), @@ -508,6 +508,7 @@ impl Message { approx_characters, context, ) + .await } pub fn has_deviating_timestamp(&self) -> bool { @@ -595,7 +596,7 @@ impl Message { self.param.set_int(Param::Duration, duration); } - pub fn latefiling_mediasize( + pub async fn latefiling_mediasize( &mut self, context: &Context, width: i32, @@ -609,7 +610,7 @@ impl Message { if duration > 0 { self.param.set_int(Param::Duration, duration); } - self.save_param_to_disk(context); + self.save_param_to_disk(context).await; } pub async fn save_param_to_disk(&mut self, context: &Context) -> bool { @@ -617,7 +618,7 @@ impl Message { .sql .execute( "UPDATE msgs SET param=? WHERE id=?;", - params![self.param.to_string(), self.id], + paramsv![self.param.to_string(), self.id], ) .await .is_ok() @@ -740,7 +741,7 @@ impl MessageState { impl Lot { /* library-internal */ /* in practice, the user additionally cuts the string himself pixel-accurate */ - pub fn fill( + pub async fn fill( &mut self, msg: &mut Message, chat: &Chat, @@ -748,14 +749,26 @@ impl Lot { context: &Context, ) { if msg.state == MessageState::OutDraft { - self.text1 = Some(context.stock_str(StockMessage::Draft).to_owned().into()); + self.text1 = Some( + context + .stock_str(StockMessage::Draft) + .await + .to_owned() + .into(), + ); self.text1_meaning = Meaning::Text1Draft; } else if msg.from_id == DC_CONTACT_ID_SELF { if msg.is_info() || chat.is_self_talk() { self.text1 = None; self.text1_meaning = Meaning::None; } else { - self.text1 = Some(context.stock_str(StockMessage::SelfMsg).to_owned().into()); + self.text1 = Some( + context + .stock_str(StockMessage::SelfMsg) + .await + .to_owned() + .into(), + ); self.text1_meaning = Meaning::Text1Self; } } else if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup { @@ -778,13 +791,16 @@ impl Lot { } } - self.text2 = Some(get_summarytext_by_raw( - msg.viewtype, - msg.text.as_ref(), - &msg.param, - SUMMARY_CHARACTERS, - context, - )); + self.text2 = Some( + get_summarytext_by_raw( + msg.viewtype, + msg.text.as_ref(), + &msg.param, + SUMMARY_CHARACTERS, + context, + ) + .await, + ); self.timestamp = msg.get_timestamp(); self.state = msg.state.into(); @@ -806,7 +822,7 @@ pub async fn get_msg_info(context: &Context, msg_id: MsgId) -> String { .query_get_value( context, "SELECT txt_raw FROM msgs WHERE id=?;", - params![msg_id], + paramsv![msg_id], ) .await; @@ -843,21 +859,26 @@ pub async fn get_msg_info(context: &Context, msg_id: MsgId) -> String { return ret; } - if let Ok(rows) = context.sql.query_map( - "SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?;", - params![msg_id], - |row| { - let contact_id: i32 = row.get(0)?; - let ts: i64 = row.get(1)?; - Ok((contact_id, ts)) - }, - |rows| rows.collect::, _>>().map_err(Into::into), - ) { + if let Ok(rows) = context + .sql + .query_map( + "SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?;", + paramsv![msg_id], + |row| { + let contact_id: i32 = row.get(0)?; + let ts: i64 = row.get(1)?; + Ok((contact_id, ts)) + }, + |rows| rows.collect::, _>>().map_err(Into::into), + ) + .await + { for (contact_id, ts) in rows { let fts = dc_timestamp_to_str(ts); ret += &format!("Read: {}", fts); let name = Contact::load_from_db(context, contact_id as u32) + .await .map(|contact| contact.get_name_n_addr()) .unwrap_or_default(); @@ -944,22 +965,25 @@ pub fn guess_msgtype_from_suffix(path: &Path) -> Option<(Viewtype, &str)> { Some(info) } -pub fn get_mime_headers(context: &Context, msg_id: MsgId) -> Option { - context.sql.query_get_value( - context, - "SELECT mime_headers FROM msgs WHERE id=?;", - params![msg_id], - ) +pub async fn get_mime_headers(context: &Context, msg_id: MsgId) -> Option { + context + .sql + .query_get_value( + context, + "SELECT mime_headers FROM msgs WHERE id=?;", + paramsv![msg_id], + ) + .await } pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) { for msg_id in msg_ids.iter() { if let Ok(msg) = Message::load_from_db(context, *msg_id).await { if msg.location_id > 0 { - delete_poi_location(context, msg.location_id); + delete_poi_location(context, msg.location_id).await; } } - update_msg_chat_id(context, *msg_id, ChatId::new(DC_CHAT_ID_TRASH)); + update_msg_chat_id(context, *msg_id, ChatId::new(DC_CHAT_ID_TRASH)).await; job::add( context, Action::DeleteMsgOnImap, @@ -985,7 +1009,7 @@ async fn update_msg_chat_id(context: &Context, msg_id: MsgId, chat_id: ChatId) - .sql .execute( "UPDATE msgs SET chat_id=? WHERE id=?;", - params![chat_id, msg_id], + paramsv![chat_id, msg_id], ) .await .is_ok() @@ -996,7 +1020,7 @@ async fn delete_poi_location(context: &Context, location_id: u32) -> bool { .sql .execute( "DELETE FROM locations WHERE independent = 1 AND id=?;", - params![location_id as i32], + paramsv![location_id as i32], ) .await .is_ok() @@ -1020,7 +1044,7 @@ pub async fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool { let mut msgs = Vec::with_capacity(msg_ids.len()); for id in msg_ids.iter() { - let query_res = stmt.query_row(params![*id], |row| { + let query_res = stmt.query_row(paramsv![*id], |row| { Ok(( row.get::<_, MessageState>("state")?, row.get::<_, Option>("blocked")? @@ -1046,7 +1070,7 @@ pub async fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool { for (id, curr_state, curr_blocked) in msgs.into_iter() { if curr_blocked == Blocked::Not { if curr_state == MessageState::InFresh || curr_state == MessageState::InNoticed { - update_msg_state(context, *id, MessageState::InSeen); + update_msg_state(context, *id, MessageState::InSeen).await; info!(context, "Seen message {}.", id); job::add( @@ -1060,7 +1084,7 @@ pub async fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool { send_event = true; } } else if curr_state == MessageState::InFresh { - update_msg_state(context, *id, MessageState::InNoticed); + update_msg_state(context, *id, MessageState::InNoticed).await; send_event = true; } } @@ -1080,29 +1104,31 @@ pub async fn update_msg_state(context: &Context, msg_id: MsgId, state: MessageSt .sql .execute( "UPDATE msgs SET state=? WHERE id=?;", - params![state, msg_id], + paramsv![state, msg_id], ) .await .is_ok() } -pub fn star_msgs(context: &Context, msg_ids: &[MsgId], star: bool) -> bool { +pub async fn star_msgs(context: &Context, msg_ids: &[MsgId], star: bool) -> bool { if msg_ids.is_empty() { return false; } context .sql - .prepare("UPDATE msgs SET starred=? WHERE id=?;", |mut stmt, _| { + .with_conn(|conn| { + let mut stmt = conn.prepare("UPDATE msgs SET starred=? WHERE id=?;")?; for msg_id in msg_ids.iter() { - stmt.execute(params![star as i32, *msg_id])?; + stmt.execute(paramsv![star as i32, *msg_id])?; } Ok(()) }) + .await .is_ok() } /// Returns a summary test. -pub fn get_summarytext_by_raw( +pub async fn get_summarytext_by_raw( viewtype: Viewtype, text: Option>, param: &Params, @@ -1111,16 +1137,20 @@ pub fn get_summarytext_by_raw( ) -> String { let mut append_text = true; let prefix = match viewtype { - Viewtype::Image => context.stock_str(StockMessage::Image).into_owned(), - Viewtype::Gif => context.stock_str(StockMessage::Gif).into_owned(), - Viewtype::Sticker => context.stock_str(StockMessage::Sticker).into_owned(), - Viewtype::Video => context.stock_str(StockMessage::Video).into_owned(), - Viewtype::Voice => context.stock_str(StockMessage::VoiceMessage).into_owned(), + Viewtype::Image => context.stock_str(StockMessage::Image).await.into_owned(), + Viewtype::Gif => context.stock_str(StockMessage::Gif).await.into_owned(), + Viewtype::Sticker => context.stock_str(StockMessage::Sticker).await.into_owned(), + Viewtype::Video => context.stock_str(StockMessage::Video).await.into_owned(), + Viewtype::Voice => context + .stock_str(StockMessage::VoiceMessage) + .await + .into_owned(), Viewtype::Audio | Viewtype::File => { if param.get_cmd() == SystemMessage::AutocryptSetupMessage { append_text = false; context .stock_str(StockMessage::AcSetupMsgSubject) + .await .to_string() } else { let file_name: String = param @@ -1131,11 +1161,13 @@ pub fn get_summarytext_by_raw( .map(|fname| fname.to_string_lossy().into_owned()) }) .unwrap_or_else(|| String::from("ErrFileName")); - let label = context.stock_str(if viewtype == Viewtype::Audio { - StockMessage::Audio - } else { - StockMessage::File - }); + let label = context + .stock_str(if viewtype == Viewtype::Audio { + StockMessage::Audio + } else { + StockMessage::File + }) + .await; format!("{} – {}", label, file_name) } } @@ -1144,7 +1176,7 @@ pub fn get_summarytext_by_raw( "".to_string() } else { append_text = false; - context.stock_str(StockMessage::Location).to_string() + context.stock_str(StockMessage::Location).await.to_string() } } }; @@ -1176,16 +1208,19 @@ pub fn get_summarytext_by_raw( // Context functions to work with messages -pub fn exists(context: &Context, msg_id: MsgId) -> bool { +pub async fn exists(context: &Context, msg_id: MsgId) -> bool { if msg_id.is_special() { return false; } - let chat_id: Option = context.sql.query_get_value( - context, - "SELECT chat_id FROM msgs WHERE id=?;", - params![msg_id], - ); + let chat_id: Option = context + .sql + .query_get_value( + context, + "SELECT chat_id FROM msgs WHERE id=?;", + paramsv![msg_id], + ) + .await; if let Some(chat_id) = chat_id { !chat_id.is_trash() @@ -1208,7 +1243,7 @@ pub async fn set_msg_failed(context: &Context, msg_id: MsgId, error: Option("msg_id")?, - row.get::<_, ChatId>("chat_id")?, - row.get::<_, Chattype>("type")?, - row.get::<_, MessageState>("state")?, - )) - }, - ); + let res = context + .sql + .query_row( + concat!( + "SELECT", + " m.id AS msg_id,", + " c.id AS chat_id,", + " c.type AS type,", + " m.state AS state", + " FROM msgs m LEFT JOIN chats c ON m.chat_id=c.id", + " WHERE rfc724_mid=? AND from_id=1", + " ORDER BY m.id;" + ), + paramsv![rfc724_mid], + |row| { + Ok(( + row.get::<_, MsgId>("msg_id")?, + row.get::<_, ChatId>("chat_id")?, + row.get::<_, Chattype>("type")?, + row.get::<_, MessageState>("state")?, + )) + }, + ) + .await; if let Err(ref err) = res { info!(context, "Failed to select MDN {:?}", err); } @@ -1268,30 +1306,34 @@ pub fn mdn_from_ext( .sql .exists( "SELECT contact_id FROM msgs_mdns WHERE msg_id=? AND contact_id=?;", - params![msg_id, from_id as i32,], + paramsv![msg_id, from_id as i32,], ) + .await .unwrap_or_default(); if !mdn_already_in_table { context.sql.execute( "INSERT INTO msgs_mdns (msg_id, contact_id, timestamp_sent) VALUES (?, ?, ?);", - params![msg_id, from_id as i32, timestamp_sent], - ).unwrap_or_default(); // TODO: better error handling + paramsv![msg_id, from_id as i32, timestamp_sent], + ) + .await + .unwrap_or_default(); // TODO: better error handling } // Normal chat? that's quite easy. if chat_type == Chattype::Single { - update_msg_state(context, msg_id, MessageState::OutMdnRcvd); + update_msg_state(context, msg_id, MessageState::OutMdnRcvd).await; read_by_all = true; } else { // send event about new state let ist_cnt = context .sql - .query_get_value::<_, isize>( + .query_get_value::( context, "SELECT COUNT(*) FROM msgs_mdns WHERE msg_id=?;", - params![msg_id], + paramsv![msg_id], ) + .await .unwrap_or_default() as usize; /* Groupsize: Min. MDNs @@ -1306,9 +1348,9 @@ pub fn mdn_from_ext( (S=Sender, R=Recipient) */ // for rounding, SELF is already included! - let soll_cnt = (chat::get_chat_contact_cnt(context, chat_id) + 1) / 2; + let soll_cnt = (chat::get_chat_contact_cnt(context, chat_id).await + 1) / 2; if ist_cnt >= soll_cnt { - update_msg_state(context, msg_id, MessageState::OutMdnRcvd); + update_msg_state(context, msg_id, MessageState::OutMdnRcvd).await; read_by_all = true; } // else wait for more receipts } @@ -1323,14 +1365,18 @@ pub fn mdn_from_ext( } /// The number of messages assigned to real chat (!=deaddrop, !=trash) -pub fn get_real_msg_cnt(context: &Context) -> i32 { - match context.sql.query_row( - "SELECT COUNT(*) \ +pub async fn get_real_msg_cnt(context: &Context) -> i32 { + match context + .sql + .query_row( + "SELECT COUNT(*) \ FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \ WHERE m.id>9 AND m.chat_id>9 AND c.blocked=0;", - rusqlite::NO_PARAMS, - |row| row.get(0), - ) { + paramsv![], + |row| row.get(0), + ) + .await + { Ok(res) => res, Err(err) => { error!(context, "dc_get_real_msg_cnt() failed. {}", err); @@ -1339,14 +1385,18 @@ pub fn get_real_msg_cnt(context: &Context) -> i32 { } } -pub fn get_deaddrop_msg_cnt(context: &Context) -> usize { - match context.sql.query_row( - "SELECT COUNT(*) \ +pub async fn get_deaddrop_msg_cnt(context: &Context) -> usize { + match context + .sql + .query_row( + "SELECT COUNT(*) \ FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \ WHERE c.blocked=2;", - rusqlite::NO_PARAMS, - |row| row.get::<_, isize>(0), - ) { + paramsv![], + |row| row.get::<_, isize>(0), + ) + .await + { Ok(res) => res as usize, Err(err) => { error!(context, "dc_get_deaddrop_msg_cnt() failed. {}", err); @@ -1355,13 +1405,17 @@ pub fn get_deaddrop_msg_cnt(context: &Context) -> usize { } } -pub fn rfc724_mid_cnt(context: &Context, rfc724_mid: &str) -> i32 { +pub async fn rfc724_mid_cnt(context: &Context, rfc724_mid: &str) -> i32 { // check the number of messages with the same rfc724_mid - match context.sql.query_row( - "SELECT COUNT(*) FROM msgs WHERE rfc724_mid=?;", - &[rfc724_mid], - |row| row.get(0), - ) { + match context + .sql + .query_row( + "SELECT COUNT(*) FROM msgs WHERE rfc724_mid=?;", + paramsv![rfc724_mid], + |row| row.get(0), + ) + .await + { Ok(res) => res, Err(err) => { error!(context, "dc_get_rfc724_mid_cnt() failed. {}", err); @@ -1370,17 +1424,17 @@ pub fn rfc724_mid_cnt(context: &Context, rfc724_mid: &str) -> i32 { } } -pub(crate) fn rfc724_mid_exists( +pub(crate) async fn rfc724_mid_exists( context: &Context, rfc724_mid: &str, ) -> Result<(String, u32, MsgId), Error> { ensure!(!rfc724_mid.is_empty(), "empty rfc724_mid"); - context + let res = context .sql .query_row( "SELECT server_folder, server_uid, id FROM msgs WHERE rfc724_mid=?", - &[rfc724_mid], + paramsv![rfc724_mid], |row| { let server_folder = row.get::<_, Option>(0)?.unwrap_or_default(); let server_uid = row.get(1)?; @@ -1389,19 +1443,25 @@ pub(crate) fn rfc724_mid_exists( Ok((server_folder, server_uid, msg_id)) }, ) - .map_err(Into::into) + .await?; + + Ok(res) } -pub fn update_server_uid( +pub async fn update_server_uid( context: &Context, rfc724_mid: &str, server_folder: impl AsRef, server_uid: u32, ) { - match context.sql.execute( - "UPDATE msgs SET server_folder=?, server_uid=? WHERE rfc724_mid=?;", - params![server_folder.as_ref(), server_uid, rfc724_mid], - ) { + match context + .sql + .execute( + "UPDATE msgs SET server_folder=?, server_uid=? WHERE rfc724_mid=?;", + paramsv![server_folder.as_ref().to_string(), server_uid, rfc724_mid], + ) + .await + { Ok(_) => {} Err(err) => { warn!(context, "msg: failed to update server_uid: {}", err); @@ -1432,7 +1492,7 @@ mod tests { async fn test_prepare_message_and_send() { use crate::config::Config; - let d = test::dummy_context(); + let d = test::dummy_context().await; let ctx = &d.ctx; let contact = Contact::create(ctx, "", "dest@example.com") @@ -1454,9 +1514,9 @@ mod tests { assert_eq!(_msg2.get_filemime(), None); } - #[test] - pub fn test_get_summarytext_by_raw() { - let d = test::dummy_context(); + #[async_std::test] + async fn test_get_summarytext_by_raw() { + let d = test::dummy_context().await; let ctx = &d.ctx; let some_text = Some("bla bla".to_string()); @@ -1467,62 +1527,69 @@ mod tests { some_file.set(Param::File, "foo.bar"); assert_eq!( - get_summarytext_by_raw(Viewtype::Text, some_text.as_ref(), &Params::new(), 50, &ctx), + get_summarytext_by_raw(Viewtype::Text, some_text.as_ref(), &Params::new(), 50, &ctx) + .await, "bla bla" // for simple text, the type is not added to the summary ); assert_eq!( - get_summarytext_by_raw(Viewtype::Image, no_text.as_ref(), &some_file, 50, &ctx,), + get_summarytext_by_raw(Viewtype::Image, no_text.as_ref(), &some_file, 50, &ctx).await, "Image" // file names are not added for images ); assert_eq!( - get_summarytext_by_raw(Viewtype::Video, no_text.as_ref(), &some_file, 50, &ctx,), + get_summarytext_by_raw(Viewtype::Video, no_text.as_ref(), &some_file, 50, &ctx).await, "Video" // file names are not added for videos ); assert_eq!( - get_summarytext_by_raw(Viewtype::Gif, no_text.as_ref(), &some_file, 50, &ctx,), + get_summarytext_by_raw(Viewtype::Gif, no_text.as_ref(), &some_file, 50, &ctx,).await, "GIF" // file names are not added for GIFs ); assert_eq!( - get_summarytext_by_raw(Viewtype::Sticker, no_text.as_ref(), &some_file, 50, &ctx,), + get_summarytext_by_raw(Viewtype::Sticker, no_text.as_ref(), &some_file, 50, &ctx,) + .await, "Sticker" // file names are not added for stickers ); assert_eq!( - get_summarytext_by_raw(Viewtype::Voice, empty_text.as_ref(), &some_file, 50, &ctx,), + get_summarytext_by_raw(Viewtype::Voice, empty_text.as_ref(), &some_file, 50, &ctx,) + .await, "Voice message" // file names are not added for voice messages, empty text is skipped ); assert_eq!( - get_summarytext_by_raw(Viewtype::Voice, no_text.as_ref(), &mut some_file, 50, &ctx), + get_summarytext_by_raw(Viewtype::Voice, no_text.as_ref(), &mut some_file, 50, &ctx) + .await, "Voice message" // file names are not added for voice messages ); assert_eq!( - get_summarytext_by_raw(Viewtype::Voice, some_text.as_ref(), &some_file, 50, &ctx), + get_summarytext_by_raw(Viewtype::Voice, some_text.as_ref(), &some_file, 50, &ctx).await, "Voice message \u{2013} bla bla" // `\u{2013}` explicitly checks for "EN DASH" ); assert_eq!( - get_summarytext_by_raw(Viewtype::Audio, no_text.as_ref(), &mut some_file, 50, &ctx), + get_summarytext_by_raw(Viewtype::Audio, no_text.as_ref(), &mut some_file, 50, &ctx) + .await, "Audio \u{2013} foo.bar" // file name is added for audio ); assert_eq!( - get_summarytext_by_raw(Viewtype::Audio, empty_text.as_ref(), &some_file, 50, &ctx,), + get_summarytext_by_raw(Viewtype::Audio, empty_text.as_ref(), &some_file, 50, &ctx,) + .await, "Audio \u{2013} foo.bar" // file name is added for audio, empty text is not added ); assert_eq!( - get_summarytext_by_raw(Viewtype::Audio, some_text.as_ref(), &some_file, 50, &ctx), + get_summarytext_by_raw(Viewtype::Audio, some_text.as_ref(), &some_file, 50, &ctx).await, "Audio \u{2013} foo.bar \u{2013} bla bla" // file name and text added for audio ); assert_eq!( - get_summarytext_by_raw(Viewtype::File, some_text.as_ref(), &mut some_file, 50, &ctx), + get_summarytext_by_raw(Viewtype::File, some_text.as_ref(), &mut some_file, 50, &ctx) + .await, "File \u{2013} foo.bar \u{2013} bla bla" // file name is added for files ); @@ -1530,7 +1597,7 @@ mod tests { asm_file.set(Param::File, "foo.bar"); asm_file.set_cmd(SystemMessage::AutocryptSetupMessage); assert_eq!( - get_summarytext_by_raw(Viewtype::File, no_text.as_ref(), &mut asm_file, 50, &ctx), + get_summarytext_by_raw(Viewtype::File, no_text.as_ref(), &mut asm_file, 50, &ctx).await, "Autocrypt Setup Message" // file name is not added for autocrypt setup messages ); } diff --git a/src/mimefactory.rs b/src/mimefactory.rs index 9841108c3..b831f7c7b 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -65,44 +65,51 @@ pub struct RenderedEmail { } impl<'a, 'b> MimeFactory<'a, 'b> { - pub fn from_msg( + pub async fn from_msg( context: &'a Context, msg: &'b Message, attach_selfavatar: bool, ) -> Result, Error> { - let chat = Chat::load_from_db(context, msg.chat_id)?; + let chat = Chat::load_from_db(context, msg.chat_id).await?; let from_addr = context .get_config(Config::ConfiguredAddr) + .await + .unwrap_or_default(); + let from_displayname = context + .get_config(Config::Displayname) + .await .unwrap_or_default(); - let from_displayname = context.get_config(Config::Displayname).unwrap_or_default(); let mut recipients = Vec::with_capacity(5); let mut req_mdn = false; if chat.is_self_talk() { recipients.push((from_displayname.to_string(), from_addr.to_string())); } else { - context.sql.query_map( - "SELECT c.authname, c.addr \ + context + .sql + .query_map( + "SELECT c.authname, c.addr \ FROM chats_contacts cc \ LEFT JOIN contacts c ON cc.contact_id=c.id \ WHERE cc.chat_id=? AND cc.contact_id>9;", - params![msg.chat_id], - |row| { - let authname: String = row.get(0)?; - let addr: String = row.get(1)?; - Ok((authname, addr)) - }, - |rows| { - for row in rows { - let (authname, addr) = row?; - if !recipients_contain_addr(&recipients, &addr) { - recipients.push((authname, addr)); + paramsv![msg.chat_id], + |row| { + let authname: String = row.get(0)?; + let addr: String = row.get(1)?; + Ok((authname, addr)) + }, + |rows| { + for row in rows { + let (authname, addr) = row?; + if !recipients_contain_addr(&recipients, &addr) { + recipients.push((authname, addr)); + } } - } - Ok(()) - }, - )?; + Ok(()) + }, + ) + .await?; let command = msg.param.get_cmd(); @@ -112,6 +119,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> { let self_addr = context .get_config(Config::ConfiguredAddr) + .await .unwrap_or_default(); if !email_to_remove.is_empty() @@ -123,31 +131,39 @@ impl<'a, 'b> MimeFactory<'a, 'b> { } if command != SystemMessage::AutocryptSetupMessage && command != SystemMessage::SecurejoinMessage - && context.get_config_bool(Config::MdnsEnabled) + && context.get_config_bool(Config::MdnsEnabled).await { req_mdn = true; } } - let (in_reply_to, references) = context.sql.query_row( - "SELECT mime_in_reply_to, mime_references FROM msgs WHERE id=?", - params![msg.id], - |row| { - let in_reply_to: String = row.get(0)?; - let references: String = row.get(1)?; + let (in_reply_to, references) = context + .sql + .query_row( + "SELECT mime_in_reply_to, mime_references FROM msgs WHERE id=?", + paramsv![msg.id], + |row| { + let in_reply_to: String = row.get(0)?; + let references: String = row.get(1)?; - Ok(( - render_rfc724_mid_list(&in_reply_to), - render_rfc724_mid_list(&references), - )) - }, - )?; + Ok(( + render_rfc724_mid_list(&in_reply_to), + render_rfc724_mid_list(&references), + )) + }, + ) + .await?; + let default_str = context + .stock_str(StockMessage::StatusLine) + .await + .to_string(); let factory = MimeFactory { from_addr, from_displayname, selfstatus: context .get_config(Config::Selfstatus) - .unwrap_or_else(|| context.stock_str(StockMessage::StatusLine).to_string()), + .await + .unwrap_or_else(|| default_str), recipients, timestamp: msg.timestamp_sort, loaded: Loaded::Message { chat }, @@ -162,29 +178,42 @@ impl<'a, 'b> MimeFactory<'a, 'b> { Ok(factory) } - pub fn from_mdn( + pub async fn from_mdn( context: &'a Context, msg: &'b Message, additional_msg_ids: Vec, - ) -> Result { + ) -> Result, Error> { ensure!(!msg.chat_id.is_special(), "Invalid chat id"); - let contact = Contact::load_from_db(context, msg.from_id)?; + let contact = Contact::load_from_db(context, msg.from_id).await?; + let from_addr = context + .get_config(Config::ConfiguredAddr) + .await + .unwrap_or_default(); + let from_displayname = context + .get_config(Config::Displayname) + .await + .unwrap_or_default(); + let default_str = context + .stock_str(StockMessage::StatusLine) + .await + .to_string(); + let selfstatus = context + .get_config(Config::Selfstatus) + .await + .unwrap_or_else(|| default_str); + let timestamp = dc_create_smeared_timestamp(context).await; - Ok(MimeFactory { + let res = MimeFactory::<'a, 'b> { context, - from_addr: context - .get_config(Config::ConfiguredAddr) - .unwrap_or_default(), - from_displayname: context.get_config(Config::Displayname).unwrap_or_default(), - selfstatus: context - .get_config(Config::Selfstatus) - .unwrap_or_else(|| context.stock_str(StockMessage::StatusLine).to_string()), + from_addr, + from_displayname, + selfstatus, recipients: vec![( contact.get_authname().to_string(), contact.get_addr().to_string(), )], - timestamp: dc_create_smeared_timestamp(context), + timestamp, loaded: Loaded::MDN { additional_msg_ids }, msg, in_reply_to: String::default(), @@ -192,7 +221,9 @@ impl<'a, 'b> MimeFactory<'a, 'b> { req_mdn: false, last_added_location_id: 0, attach_selfavatar: false, - }) + }; + + Ok(res) } async fn peerstates_for_recipients(&self) -> Result>, &str)>, Error> { @@ -202,17 +233,19 @@ impl<'a, 'b> MimeFactory<'a, 'b> { .await .ok_or_else(|| format_err!("Not configured"))?; - Ok(self + let mut res = Vec::new(); + for (_, addr) in self .recipients .iter() .filter(|(_, addr)| addr != &self_addr) - .map(|(_, addr)| { - ( - Peerstate::from_addr(self.context, &self.context.sql, addr), - addr.as_str(), - ) - }) - .collect()) + { + res.push(( + Peerstate::from_addr(self.context, &self.context.sql, addr).await, + addr.as_str(), + )); + } + + Ok(res) } fn is_e2ee_guaranteed(&self) -> bool { @@ -272,11 +305,11 @@ impl<'a, 'b> MimeFactory<'a, 'b> { } } - fn should_do_gossip(&self) -> bool { + async fn should_do_gossip(&self) -> bool { match &self.loaded { Loaded::Message { chat } => { // beside key- and member-changes, force re-gossip every 48 hours - let gossiped_timestamp = chat.get_gossiped_timestamp(self.context); + let gossiped_timestamp = chat.get_gossiped_timestamp(self.context).await; if time() > gossiped_timestamp + (2 * 24 * 60 * 60) { return true; } @@ -317,12 +350,13 @@ impl<'a, 'b> MimeFactory<'a, 'b> { } } - fn subject_str(&self) -> String { + async fn subject_str(&self) -> String { match self.loaded { Loaded::Message { ref chat } => { if self.msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage { self.context .stock_str(StockMessage::AcSetupMsgSubject) + .await .into_owned() } else if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup { let re = if self.in_reply_to.is_empty() { @@ -338,12 +372,17 @@ impl<'a, 'b> MimeFactory<'a, 'b> { &self.msg.param, 32, self.context, - ); + ) + .await; let raw_subject = raw.lines().next().unwrap_or_default(); format!("Chat: {}", raw_subject) } } - Loaded::MDN { .. } => self.context.stock_str(StockMessage::ReadRcpt).into_owned(), + Loaded::MDN { .. } => self + .context + .stock_str(StockMessage::ReadRcpt) + .await + .into_owned(), } } @@ -354,7 +393,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> { .collect() } - pub fn render(mut self) -> Result { + pub async fn render(mut self) -> Result { // Headers that are encrypted // - Chat-*, except Chat-Version // - Secure-Join* @@ -433,17 +472,18 @@ impl<'a, 'b> MimeFactory<'a, 'b> { let min_verified = self.min_verified(); let grpimage = self.grpimage(); let force_plaintext = self.should_force_plaintext(); - let subject_str = self.subject_str(); + let subject_str = self.subject_str().await; let e2ee_guaranteed = self.is_e2ee_guaranteed(); - let mut encrypt_helper = EncryptHelper::new(self.context)?; + let mut encrypt_helper = EncryptHelper::new(self.context).await?; let subject = encode_words(&subject_str); let mut message = match self.loaded { Loaded::Message { .. } => { - self.render_message(&mut protected_headers, &mut unprotected_headers, &grpimage)? + self.render_message(&mut protected_headers, &mut unprotected_headers, &grpimage) + .await? } - Loaded::MDN { .. } => self.render_mdn()?, + Loaded::MDN { .. } => self.render_mdn().await?, }; if force_plaintext != ForcePlaintext::NoAutocryptHeader as i32 { @@ -454,7 +494,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> { protected_headers.push(Header::new("Subject".into(), subject)); - let peerstates = self.peerstates_for_recipients()?; + let peerstates = self.peerstates_for_recipients().await?; let should_encrypt = encrypt_helper.should_encrypt(self.context, e2ee_guaranteed, &peerstates)?; let is_encrypted = should_encrypt && force_plaintext == 0; @@ -482,7 +522,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> { let outer_message = if is_encrypted { // Add gossip headers in chats with multiple recipients - if peerstates.len() > 1 && self.should_do_gossip() { + if peerstates.len() > 1 && self.should_do_gossip().await { for peerstate in peerstates.iter().filter_map(|(state, _)| state.as_ref()) { if peerstate.peek_key(min_verified).is_some() { if let Some(header) = peerstate.render_gossip_header(min_verified) { @@ -530,8 +570,9 @@ impl<'a, 'b> MimeFactory<'a, 'b> { println!("{}", raw_message); } - let encrypted = - encrypt_helper.encrypt(self.context, min_verified, message, &peerstates)?; + let encrypted = encrypt_helper + .encrypt(self.context, min_verified, message, &peerstates) + .await?; outer_message = outer_message .child( @@ -603,9 +644,9 @@ impl<'a, 'b> MimeFactory<'a, 'b> { Some(part) } - fn get_location_kml_part(&mut self) -> Result { + async fn get_location_kml_part(&mut self) -> Result { let (kml_content, last_added_location_id) = - location::get_kml(self.context, self.msg.chat_id)?; + location::get_kml(self.context, self.msg.chat_id).await?; let part = PartBuilder::new() .content_type( &"application/vnd.google-earth.kml+xml" @@ -625,7 +666,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> { } #[allow(clippy::cognitive_complexity)] - fn render_message( + async fn render_message( &mut self, protected_headers: &mut Vec
, unprotected_headers: &mut Vec
, @@ -720,6 +761,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> { placeholdertext = Some( self.context .stock_str(StockMessage::AcSetupMsgBody) + .await .to_string(), ); } @@ -856,8 +898,8 @@ impl<'a, 'b> MimeFactory<'a, 'b> { parts.push(msg_kml_part); } - if location::is_sending_locations_to_chat(context, self.msg.chat_id) { - match self.get_location_kml_part() { + if location::is_sending_locations_to_chat(context, self.msg.chat_id).await { + match self.get_location_kml_part().await { Ok(part) => parts.push(part), Err(err) => { warn!(context, "mimefactory: could not send location: {}", err); @@ -866,7 +908,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> { } if self.attach_selfavatar { - match context.get_config(Config::Selfavatar) { + match context.get_config(Config::Selfavatar).await { Some(path) => match build_selfavatar_file(context, &path) { Ok((part, filename)) => { parts.push(part); @@ -893,7 +935,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> { } /// Render an MDN - fn render_mdn(&mut self) -> Result { + async fn render_mdn(&mut self) -> Result { // RFC 6522, this also requires the `report-type` parameter which is equal // to the MIME subtype of the second body part of the multipart/report // @@ -928,13 +970,15 @@ impl<'a, 'b> MimeFactory<'a, 'b> { { self.context .stock_str(StockMessage::EncryptedMsg) + .await .into_owned() } else { - self.msg.get_summarytext(self.context, 32) + self.msg.get_summarytext(self.context, 32).await }; let p2 = self .context - .stock_string_repl_str(StockMessage::ReadRcptMailBody, p1); + .stock_string_repl_str(StockMessage::ReadRcptMailBody, p1) + .await; let message_text = format!("{}\r\n", p2); message = message.child( PartBuilder::new() diff --git a/src/mimeparser.rs b/src/mimeparser.rs index 51f20bdef..0836ff6bf 100644 --- a/src/mimeparser.rs +++ b/src/mimeparser.rs @@ -1,4 +1,6 @@ use std::collections::{HashMap, HashSet}; +use std::future::Future; +use std::pin::Pin; use deltachat_derive::{FromSql, ToSql}; use lettre_email::mime::{self, Mime}; @@ -156,7 +158,7 @@ impl MimeMessage { user_avatar: None, group_avatar: None, }; - parser.parse_mime_recursive(context, &mail)?; + parser.parse_mime_recursive(context, &mail).await?; parser.parse_headers(context)?; Ok(parser) @@ -405,63 +407,69 @@ impl MimeMessage { None } - fn parse_mime_recursive( - &mut self, - context: &Context, - mail: &mailparse::ParsedMail<'_>, - ) -> Result { - if mail.ctype.params.get("protected-headers").is_some() { - if mail.ctype.mimetype == "text/rfc822-headers" { - warn!( + fn parse_mime_recursive<'a>( + &'a mut self, + context: &'a Context, + mail: &'a mailparse::ParsedMail<'a>, + ) -> Pin> + 'a + Send>> { + use futures::future::FutureExt; + + // Boxed future to deal with recursion + async move { + if mail.ctype.params.get("protected-headers").is_some() { + if mail.ctype.mimetype == "text/rfc822-headers" { + warn!( context, "Protected headers found in text/rfc822-headers attachment: Will be ignored.", ); - return Ok(false); - } - - warn!(context, "Ignoring nested protected headers"); - } - - enum MimeS { - Multiple, - Single, - Message, - } - - let mimetype = mail.ctype.mimetype.to_lowercase(); - - let m = if mimetype.starts_with("multipart") { - if mail.ctype.params.get("boundary").is_some() { - MimeS::Multiple - } else { - MimeS::Single - } - } else if mimetype.starts_with("message") { - if mimetype == "message/rfc822" { - MimeS::Message - } else { - MimeS::Single - } - } else { - MimeS::Single - }; - - match m { - MimeS::Multiple => self.handle_multiple(context, mail), - MimeS::Message => { - let raw = mail.get_body_raw()?; - if raw.is_empty() { return Ok(false); } - let mail = mailparse::parse_mail(&raw).unwrap(); - self.parse_mime_recursive(context, &mail) + warn!(context, "Ignoring nested protected headers"); + } + + enum MimeS { + Multiple, + Single, + Message, + } + + let mimetype = mail.ctype.mimetype.to_lowercase(); + + let m = if mimetype.starts_with("multipart") { + if mail.ctype.params.get("boundary").is_some() { + MimeS::Multiple + } else { + MimeS::Single + } + } else if mimetype.starts_with("message") { + if mimetype == "message/rfc822" { + MimeS::Message + } else { + MimeS::Single + } + } else { + MimeS::Single + }; + + match m { + MimeS::Multiple => self.handle_multiple(context, mail).await, + MimeS::Message => { + let raw = mail.get_body_raw()?; + if raw.is_empty() { + return Ok(false); + } + let mail = mailparse::parse_mail(&raw).unwrap(); + + self.parse_mime_recursive(context, &mail).await + } + MimeS::Single => self.add_single_part_if_known(context, mail), } - MimeS::Single => self.add_single_part_if_known(context, mail), } + .boxed() } - fn handle_multiple( + async fn handle_multiple( &mut self, context: &Context, mail: &mailparse::ParsedMail<'_>, @@ -476,7 +484,7 @@ impl MimeMessage { (mime::MULTIPART, "alternative") => { for cur_data in &mail.subparts { if get_mime_type(cur_data)?.0 == "multipart/mixed" { - any_part_added = self.parse_mime_recursive(context, cur_data)?; + any_part_added = self.parse_mime_recursive(context, cur_data).await?; break; } } @@ -484,7 +492,7 @@ impl MimeMessage { /* search for text/plain and add this */ for cur_data in &mail.subparts { if get_mime_type(cur_data)?.0.type_() == mime::TEXT { - any_part_added = self.parse_mime_recursive(context, cur_data)?; + any_part_added = self.parse_mime_recursive(context, cur_data).await?; break; } } @@ -492,7 +500,7 @@ impl MimeMessage { if !any_part_added { /* `text/plain` not found - use the first part */ for cur_part in &mail.subparts { - if self.parse_mime_recursive(context, cur_part)? { + if self.parse_mime_recursive(context, cur_part).await? { any_part_added = true; break; } @@ -505,14 +513,14 @@ impl MimeMessage { being the first one, which may not be always true ... however, most times it seems okay. */ if let Some(first) = mail.subparts.iter().next() { - any_part_added = self.parse_mime_recursive(context, first)?; + any_part_added = self.parse_mime_recursive(context, first).await?; } } (mime::MULTIPART, "encrypted") => { // we currently do not try to decrypt non-autocrypt messages // at all. If we see an encrypted part, we set // decrypting_failed. - let msg_body = context.stock_str(StockMessage::CantDecryptMsgBody); + let msg_body = context.stock_str(StockMessage::CantDecryptMsgBody).await; let txt = format!("[{}]", msg_body); let mut part = Part::default(); @@ -535,7 +543,7 @@ impl MimeMessage { https://k9mail.github.io/2016/11/24/OpenPGP-Considerations-Part-I.html for background information why we use encrypted+signed) */ if let Some(first) = mail.subparts.iter().next() { - any_part_added = self.parse_mime_recursive(context, first)?; + any_part_added = self.parse_mime_recursive(context, first).await?; } } (mime::MULTIPART, "report") => { @@ -550,7 +558,7 @@ impl MimeMessage { /* eg. `report-type=delivery-status`; maybe we should show them as a little error icon */ if let Some(first) = mail.subparts.iter().next() { - any_part_added = self.parse_mime_recursive(context, first)?; + any_part_added = self.parse_mime_recursive(context, first).await?; } } } @@ -560,7 +568,7 @@ impl MimeMessage { // Add all parts (in fact, AddSinglePartIfKnown() later check if // the parts are really supported) for cur_data in mail.subparts.iter() { - if self.parse_mime_recursive(context, cur_data)? { + if self.parse_mime_recursive(context, cur_data).await? { any_part_added = true; } } @@ -827,6 +835,7 @@ impl MimeMessage { { if let Some((chat_id, msg_id)) = message::mdn_from_ext(context, from_id, original_message_id, sent_timestamp) + .await { context.call_cb(Event::MsgRead { chat_id, msg_id }); mdn_recognized = true; @@ -876,7 +885,7 @@ async fn update_gossip_peerstates( .unwrap() .contains(&header.addr.to_lowercase()) { - let mut peerstate = Peerstate::from_addr(context, &context.sql, &header.addr); + let mut peerstate = Peerstate::from_addr(context, &context.sql, &header.addr).await; if let Some(ref mut peerstate) = peerstate { peerstate.apply_gossip(header, message_time); peerstate.save_to_db(&context.sql, false).await?; @@ -1103,21 +1112,25 @@ mod tests { } } - #[test] - fn test_dc_mimeparser_crash() { - let context = dummy_context(); + #[async_std::test] + async fn test_dc_mimeparser_crash() { + let context = dummy_context().await; let raw = include_bytes!("../test-data/message/issue_523.txt"); - let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap(); + let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]) + .await + .unwrap(); assert_eq!(mimeparser.get_subject(), None); assert_eq!(mimeparser.parts.len(), 1); } - #[test] - fn test_get_rfc724_mid_exists() { - let context = dummy_context(); + #[async_std::test] + async fn test_get_rfc724_mid_exists() { + let context = dummy_context().await; let raw = include_bytes!("../test-data/message/mail_with_message_id.txt"); - let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap(); + let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]) + .await + .unwrap(); assert_eq!( mimeparser.get_rfc724_mid(), @@ -1125,19 +1138,23 @@ mod tests { ); } - #[test] - fn test_get_rfc724_mid_not_exists() { - let context = dummy_context(); + #[async_std::test] + async fn test_get_rfc724_mid_not_exists() { + let context = dummy_context().await; let raw = include_bytes!("../test-data/message/issue_523.txt"); - let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap(); + let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]) + .await + .unwrap(); assert_eq!(mimeparser.get_rfc724_mid(), None); } - #[test] - fn test_get_recipients() { - let context = dummy_context(); + #[async_std::test] + async fn test_get_recipients() { + let context = dummy_context().await; let raw = include_bytes!("../test-data/message/mail_with_cc.txt"); - let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap(); + let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]) + .await + .unwrap(); let recipients = get_recipients(mimeparser.header.iter()); assert!(recipients.contains("abc@bcd.com")); assert!(recipients.contains("def@def.de")); @@ -1182,9 +1199,9 @@ mod tests { ); } - #[test] - fn test_parse_first_addr() { - let context = dummy_context(); + #[async_std::test] + async fn test_parse_first_addr() { + let context = dummy_context().await; let raw = b"From: hello@one.org, world@two.org\n\ Chat-Disposition-Notification-To: wrong\n\ Content-Type: text/plain\n\ @@ -1193,7 +1210,9 @@ mod tests { test1\n\ "; - let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap(); + let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]) + .await + .unwrap(); let of = mimeparser .parse_first_addr(&context.ctx, HeaderDef::From_) @@ -1205,9 +1224,9 @@ mod tests { assert!(of.is_none()); } - #[test] - fn test_mimeparser_with_context() { - let context = dummy_context(); + #[async_std::test] + async fn test_mimeparser_with_context() { + let context = dummy_context().await; let raw = b"From: hello\n\ Content-Type: multipart/mixed; boundary=\"==break==\";\n\ Subject: outer-subject\n\ @@ -1226,7 +1245,9 @@ mod tests { --==break==--\n\ \n"; - let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap(); + let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]) + .await + .unwrap(); // non-overwritten headers do not bubble up let of = mimeparser.get(HeaderDef::SecureJoinGroup).unwrap(); @@ -1246,31 +1267,31 @@ mod tests { assert_eq!(mimeparser.parts.len(), 1); } - #[test] - fn test_mimeparser_with_avatars() { - let t = dummy_context(); + #[async_std::test] + async fn test_mimeparser_with_avatars() { + let t = dummy_context().await; let raw = include_bytes!("../test-data/message/mail_attach_txt.eml"); - let mimeparser = MimeMessage::from_bytes(&t.ctx, &raw[..]).unwrap(); + let mimeparser = MimeMessage::from_bytes(&t.ctx, &raw[..]).await.unwrap(); assert_eq!(mimeparser.user_avatar, None); assert_eq!(mimeparser.group_avatar, None); let raw = include_bytes!("../test-data/message/mail_with_user_avatar.eml"); - let mimeparser = MimeMessage::from_bytes(&t.ctx, &raw[..]).unwrap(); + let mimeparser = MimeMessage::from_bytes(&t.ctx, &raw[..]).await.unwrap(); assert_eq!(mimeparser.parts.len(), 1); assert_eq!(mimeparser.parts[0].typ, Viewtype::Text); assert!(mimeparser.user_avatar.unwrap().is_change()); assert_eq!(mimeparser.group_avatar, None); let raw = include_bytes!("../test-data/message/mail_with_user_avatar_deleted.eml"); - let mimeparser = MimeMessage::from_bytes(&t.ctx, &raw[..]).unwrap(); + let mimeparser = MimeMessage::from_bytes(&t.ctx, &raw[..]).await.unwrap(); assert_eq!(mimeparser.parts.len(), 1); assert_eq!(mimeparser.parts[0].typ, Viewtype::Text); assert_eq!(mimeparser.user_avatar, Some(AvatarAction::Delete)); assert_eq!(mimeparser.group_avatar, None); let raw = include_bytes!("../test-data/message/mail_with_user_and_group_avatars.eml"); - let mimeparser = MimeMessage::from_bytes(&t.ctx, &raw[..]).unwrap(); + let mimeparser = MimeMessage::from_bytes(&t.ctx, &raw[..]).await.unwrap(); assert_eq!(mimeparser.parts.len(), 1); assert_eq!(mimeparser.parts[0].typ, Viewtype::Text); assert!(mimeparser.user_avatar.unwrap().is_change()); @@ -1280,16 +1301,18 @@ mod tests { let raw = include_bytes!("../test-data/message/mail_with_user_and_group_avatars.eml"); let raw = String::from_utf8_lossy(raw).to_string(); let raw = raw.replace("Chat-User-Avatar:", "Xhat-Xser-Xvatar:"); - let mimeparser = MimeMessage::from_bytes(&t.ctx, raw.as_bytes()).unwrap(); + let mimeparser = MimeMessage::from_bytes(&t.ctx, raw.as_bytes()) + .await + .unwrap(); assert_eq!(mimeparser.parts.len(), 1); assert_eq!(mimeparser.parts[0].typ, Viewtype::Image); assert_eq!(mimeparser.user_avatar, None); assert!(mimeparser.group_avatar.unwrap().is_change()); } - #[test] - fn test_mimeparser_message_kml() { - let context = dummy_context(); + #[async_std::test] + async fn test_mimeparser_message_kml() { + let context = dummy_context().await; let raw = b"Chat-Version: 1.0\n\ From: foo \n\ To: bar \n\ @@ -1317,7 +1340,9 @@ Content-Disposition: attachment; filename=\"message.kml\"\n\ --==break==--\n\ ;"; - let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap(); + let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]) + .await + .unwrap(); assert_eq!( mimeparser.get_subject(), Some("Location streaming".to_string()) @@ -1330,9 +1355,9 @@ Content-Disposition: attachment; filename=\"message.kml\"\n\ assert_eq!(mimeparser.parts.len(), 1); } - #[test] - fn test_parse_mdn() { - let context = dummy_context(); + #[async_std::test] + async fn test_parse_mdn() { + let context = dummy_context().await; let raw = b"Subject: =?utf-8?q?Chat=3A_Message_opened?=\n\ Date: Mon, 10 Jan 2020 00:00:00 +0000\n\ Chat-Version: 1.0\n\ @@ -1364,7 +1389,9 @@ Disposition: manual-action/MDN-sent-automatically; displayed\n\ --kJBbU58X1xeWNHgBtTbMk80M5qnV4N--\n\ "; - let message = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap(); + let message = MimeMessage::from_bytes(&context.ctx, &raw[..]) + .await + .unwrap(); assert_eq!( message.get_subject(), Some("Chat: Message opened".to_string()) @@ -1378,9 +1405,9 @@ Disposition: manual-action/MDN-sent-automatically; displayed\n\ /// /// RFC 6522 specifically allows MDNs to be nested inside /// multipart MIME messages. - #[test] - fn test_parse_multiple_mdns() { - let context = dummy_context(); + #[async_std::test] + async fn test_parse_multiple_mdns() { + let context = dummy_context().await; let raw = b"Subject: =?utf-8?q?Chat=3A_Message_opened?=\n\ Date: Mon, 10 Jan 2020 00:00:00 +0000\n\ Chat-Version: 1.0\n\ @@ -1442,7 +1469,9 @@ Disposition: manual-action/MDN-sent-automatically; displayed\n\ --outer--\n\ "; - let message = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap(); + let message = MimeMessage::from_bytes(&context.ctx, &raw[..]) + .await + .unwrap(); assert_eq!( message.get_subject(), Some("Chat: Message opened".to_string()) @@ -1452,9 +1481,9 @@ Disposition: manual-action/MDN-sent-automatically; displayed\n\ assert_eq!(message.reports.len(), 2); } - #[test] - fn test_parse_mdn_with_additional_message_ids() { - let context = dummy_context(); + #[async_std::test] + async fn test_parse_mdn_with_additional_message_ids() { + let context = dummy_context().await; let raw = b"Subject: =?utf-8?q?Chat=3A_Message_opened?=\n\ Date: Mon, 10 Jan 2020 00:00:00 +0000\n\ Chat-Version: 1.0\n\ @@ -1487,7 +1516,9 @@ Additional-Message-IDs: \n\ --kJBbU58X1xeWNHgBtTbMk80M5qnV4N--\n\ "; - let message = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap(); + let message = MimeMessage::from_bytes(&context.ctx, &raw[..]) + .await + .unwrap(); assert_eq!( message.get_subject(), Some("Chat: Message opened".to_string()) @@ -1502,9 +1533,9 @@ Additional-Message-IDs: \n\ ); } - #[test] - fn test_parse_inline_attachment() { - let context = dummy_context(); + #[async_std::test] + async fn test_parse_inline_attachment() { + let context = dummy_context().await; let raw = br#"Date: Thu, 13 Feb 2020 22:41:20 +0000 (UTC) From: sender@example.com To: receiver@example.com @@ -1529,7 +1560,9 @@ MDYyMDYxNTE1RTlDOEE4Cj4+CnN0YXJ0eHJlZgo4Mjc4CiUlRU9GCg== ------=_Part_25_46172632.1581201680436-- "#; - let message = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap(); + let message = MimeMessage::from_bytes(&context.ctx, &raw[..]) + .await + .unwrap(); assert_eq!( message.get_subject(), Some("Mail with inline attachment".to_string()) diff --git a/src/oauth2.rs b/src/oauth2.rs index e7a7c8c53..8eb0fc286 100644 --- a/src/oauth2.rs +++ b/src/oauth2.rs @@ -48,7 +48,7 @@ struct Response { scope: Option, } -pub fn dc_get_oauth2_url( +pub async fn dc_get_oauth2_url( context: &Context, addr: impl AsRef, redirect_uri: impl AsRef, @@ -61,6 +61,7 @@ pub fn dc_get_oauth2_url( "oauth2_pending_redirect_uri", Some(redirect_uri.as_ref()), ) + .await .is_err() { return None; @@ -76,7 +77,7 @@ pub fn dc_get_oauth2_url( // The following function may block due http-requests; // must not be called from the main thread or by the ui! -pub fn dc_get_oauth2_access_token( +pub async fn dc_get_oauth2_access_token( context: &Context, addr: impl AsRef, code: impl AsRef, @@ -84,11 +85,14 @@ pub fn dc_get_oauth2_access_token( ) -> Option { if let Some(oauth2) = Oauth2::from_address(addr) { let lock = context.oauth2_critical.clone(); - let _l = lock.lock().unwrap(); + let _l = lock.lock().await; // read generated token - if !regenerate && !is_expired(context) { - let access_token = context.sql.get_raw_config(context, "oauth2_access_token"); + if !regenerate && !is_expired(context).await { + let access_token = context + .sql + .get_raw_config(context, "oauth2_access_token") + .await; if access_token.is_some() { // success return access_token; @@ -96,10 +100,14 @@ pub fn dc_get_oauth2_access_token( } // generate new token: build & call auth url - let refresh_token = context.sql.get_raw_config(context, "oauth2_refresh_token"); + let refresh_token = context + .sql + .get_raw_config(context, "oauth2_refresh_token") + .await; let refresh_token_for = context .sql .get_raw_config(context, "oauth2_refresh_token_for") + .await .unwrap_or_else(|| "unset".into()); let (redirect_uri, token_url, update_redirect_uri_on_success) = @@ -109,6 +117,7 @@ pub fn dc_get_oauth2_access_token( context .sql .get_raw_config(context, "oauth2_pending_redirect_uri") + .await .unwrap_or_else(|| "unset".into()), oauth2.init_token, true, @@ -122,6 +131,7 @@ pub fn dc_get_oauth2_access_token( context .sql .get_raw_config(context, "oauth2_redirect_uri") + .await .unwrap_or_else(|| "unset".into()), oauth2.refresh_token, false, @@ -193,10 +203,12 @@ pub fn dc_get_oauth2_access_token( context .sql .set_raw_config(context, "oauth2_refresh_token", Some(token)) + .await .ok(); context .sql .set_raw_config(context, "oauth2_refresh_token_for", Some(code.as_ref())) + .await .ok(); } @@ -206,6 +218,7 @@ pub fn dc_get_oauth2_access_token( context .sql .set_raw_config(context, "oauth2_access_token", Some(token)) + .await .ok(); let expires_in = response .expires_in @@ -215,12 +228,14 @@ pub fn dc_get_oauth2_access_token( context .sql .set_raw_config_int64(context, "oauth2_timestamp_expires", expires_in) + .await .ok(); if update_redirect_uri_on_success { context .sql .set_raw_config(context, "oauth2_redirect_uri", Some(redirect_uri.as_ref())) + .await .ok(); } } else { @@ -235,7 +250,7 @@ pub fn dc_get_oauth2_access_token( } } -pub fn dc_get_oauth2_addr( +pub async fn dc_get_oauth2_addr( context: &Context, addr: impl AsRef, code: impl AsRef, @@ -244,12 +259,13 @@ pub fn dc_get_oauth2_addr( oauth2.get_userinfo?; if let Some(access_token) = - dc_get_oauth2_access_token(context, addr.as_ref(), code.as_ref(), false) + dc_get_oauth2_access_token(context, addr.as_ref(), code.as_ref(), false).await { let addr_out = oauth2.get_addr(context, access_token); if addr_out.is_none() { // regenerate - if let Some(access_token) = dc_get_oauth2_access_token(context, addr, code, true) { + if let Some(access_token) = dc_get_oauth2_access_token(context, addr, code, true).await + { oauth2.get_addr(context, access_token) } else { None @@ -331,10 +347,11 @@ impl Oauth2 { } } -fn is_expired(context: &Context) -> bool { +async fn is_expired(context: &Context) -> bool { let expire_timestamp = context .sql .get_raw_config_int64(context, "oauth2_timestamp_expires") + .await .unwrap_or_default(); if expire_timestamp <= 0 { @@ -393,32 +410,32 @@ mod tests { assert_eq!(Oauth2::from_address("hello@web.de"), None); } - #[test] - fn test_dc_get_oauth2_addr() { - let ctx = dummy_context(); + #[async_std::test] + async fn test_dc_get_oauth2_addr() { + let ctx = dummy_context().await; let addr = "dignifiedquire@gmail.com"; let code = "fail"; - let res = dc_get_oauth2_addr(&ctx.ctx, addr, code); + let res = dc_get_oauth2_addr(&ctx.ctx, addr, code).await; // this should fail as it is an invalid password assert_eq!(res, None); } - #[test] - fn test_dc_get_oauth2_url() { - let ctx = dummy_context(); + #[async_std::test] + async fn test_dc_get_oauth2_url() { + let ctx = dummy_context().await; let addr = "dignifiedquire@gmail.com"; let redirect_uri = "chat.delta:/com.b44t.messenger"; - let res = dc_get_oauth2_url(&ctx.ctx, addr, redirect_uri); + let res = dc_get_oauth2_url(&ctx.ctx, addr, redirect_uri).await; assert_eq!(res, Some("https://accounts.google.com/o/oauth2/auth?client_id=959970109878%2D4mvtgf6feshskf7695nfln6002mom908%2Eapps%2Egoogleusercontent%2Ecom&redirect_uri=chat%2Edelta%3A%2Fcom%2Eb44t%2Emessenger&response_type=code&scope=https%3A%2F%2Fmail.google.com%2F%20email&access_type=offline".into())); } - #[test] - fn test_dc_get_oauth2_token() { - let ctx = dummy_context(); + #[async_std::test] + async fn test_dc_get_oauth2_token() { + let ctx = dummy_context().await; let addr = "dignifiedquire@gmail.com"; let code = "fail"; - let res = dc_get_oauth2_access_token(&ctx.ctx, addr, code, false); + let res = dc_get_oauth2_access_token(&ctx.ctx, addr, code, false).await; // this should fail as it is an invalid password assert_eq!(res, None); } diff --git a/src/param.rs b/src/param.rs index 48df7ff82..ccd02549f 100644 --- a/src/param.rs +++ b/src/param.rs @@ -417,9 +417,9 @@ mod tests { assert_eq!(p1.get(Param::Forwarded).unwrap(), "cli%40deltachat.de"); } - #[test] - fn test_params_file_fs_path() { - let t = dummy_context(); + #[async_std::test] + async fn test_params_file_fs_path() { + let t = dummy_context().await; if let ParamsFile::FsPath(p) = ParamsFile::from_param(&t.ctx, "/foo/bar/baz").unwrap() { assert_eq!(p, Path::new("/foo/bar/baz")); } else { @@ -427,9 +427,9 @@ mod tests { } } - #[test] - fn test_params_file_blob() { - let t = dummy_context(); + #[async_std::test] + async fn test_params_file_blob() { + let t = dummy_context().await; if let ParamsFile::Blob(b) = ParamsFile::from_param(&t.ctx, "$BLOBDIR/foo").unwrap() { assert_eq!(b.as_name(), "$BLOBDIR/foo"); } else { @@ -438,9 +438,9 @@ mod tests { } // Tests for Params::get_file(), Params::get_path() and Params::get_blob(). - #[test] - fn test_params_get_fileparam() { - let t = dummy_context(); + #[async_std::test] + async fn test_params_get_fileparam() { + let t = dummy_context().await; let fname = t.dir.path().join("foo"); let mut p = Params::new(); p.set(Param::File, fname.to_str().unwrap()); diff --git a/src/peerstate.rs b/src/peerstate.rs index 52a591dcb..a2cbc6ef1 100644 --- a/src/peerstate.rs +++ b/src/peerstate.rs @@ -144,12 +144,16 @@ impl<'a> Peerstate<'a> { res } - pub fn from_addr(context: &'a Context, _sql: &Sql, addr: &str) -> Option { + pub async fn from_addr(context: &'a Context, _sql: &Sql, addr: &str) -> Option> { let query = "SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, verified_key, verified_key_fingerprint FROM acpeerstates WHERE addr=? COLLATE NOCASE;"; - Self::from_stmt(context, query, &[addr]) + Self::from_stmt(context, query, paramsv![addr]).await } - pub fn from_fingerprint(context: &'a Context, _sql: &Sql, fingerprint: &str) -> Option { + pub async fn from_fingerprint( + context: &'a Context, + _sql: &Sql, + fingerprint: &str, + ) -> Option> { let query = "SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, \ gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, \ verified_key, verified_key_fingerprint \ @@ -161,15 +165,16 @@ impl<'a> Peerstate<'a> { Self::from_stmt( context, query, - params![fingerprint, fingerprint, fingerprint], + paramsv![fingerprint, fingerprint, fingerprint], ) + .await } - fn from_stmt

(context: &'a Context, query: &str, params: P) -> Option - where - P: IntoIterator, - P::Item: rusqlite::ToSql, - { + async fn from_stmt( + context: &'a Context, + query: &str, + params: Vec<&dyn crate::ToSql>, + ) -> Option> { context .sql .query_row(query, params, |row| { @@ -227,6 +232,7 @@ impl<'a> Peerstate<'a> { Ok(res) }) + .await .ok() } @@ -413,7 +419,7 @@ impl<'a> Peerstate<'a> { if create { sql.execute( "INSERT INTO acpeerstates (addr) VALUES(?);", - params![self.addr], + paramsv![self.addr], ) .await?; } @@ -425,29 +431,29 @@ impl<'a> Peerstate<'a> { public_key=?, gossip_timestamp=?, gossip_key=?, public_key_fingerprint=?, gossip_key_fingerprint=?, \ verified_key=?, verified_key_fingerprint=? \ WHERE addr=?;", - params![ + paramsv![ self.last_seen, self.last_seen_autocrypt, self.prefer_encrypt as i64, self.public_key.as_ref().map(|k| k.to_bytes()), self.gossip_timestamp, self.gossip_key.as_ref().map(|k| k.to_bytes()), - &self.public_key_fingerprint, - &self.gossip_key_fingerprint, + self.public_key_fingerprint, + self.gossip_key_fingerprint, self.verified_key.as_ref().map(|k| k.to_bytes()), - &self.verified_key_fingerprint, - &self.addr, + self.verified_key_fingerprint, + self.addr, ], ).await?; } else if self.to_save == Some(ToSave::Timestamps) { sql.execute( "UPDATE acpeerstates SET last_seen=?, last_seen_autocrypt=?, gossip_timestamp=? \ WHERE addr=?;", - params![ + paramsv![ self.last_seen, self.last_seen_autocrypt, self.gossip_timestamp, - &self.addr + self.addr ], ) .await?; @@ -475,9 +481,9 @@ mod tests { use pretty_assertions::assert_eq; use tempfile::TempDir; - #[test] - fn test_peerstate_save_to_db() { - let ctx = crate::test_utils::dummy_context(); + #[async_std::test] + async fn test_peerstate_save_to_db() { + let ctx = crate::test_utils::dummy_context().await; let addr = "hello@mail.com"; let pub_key = crate::key::Key::from(alice_keypair().public); @@ -500,11 +506,12 @@ mod tests { }; assert!( - peerstate.save_to_db(&ctx.ctx.sql, true).is_ok(), + peerstate.save_to_db(&ctx.ctx.sql, true).await.is_ok(), "failed to save to db" ); let peerstate_new = Peerstate::from_addr(&ctx.ctx, &ctx.ctx.sql, addr) + .await .expect("failed to load peerstate from db"); // clear to_save, as that is not persissted @@ -512,13 +519,14 @@ mod tests { assert_eq!(peerstate, peerstate_new); let peerstate_new2 = Peerstate::from_fingerprint(&ctx.ctx, &ctx.ctx.sql, &pub_key.fingerprint()) + .await .expect("failed to load peerstate from db"); assert_eq!(peerstate, peerstate_new2); } - #[test] - fn test_peerstate_double_create() { - let ctx = crate::test_utils::dummy_context(); + #[async_std::test] + async fn test_peerstate_double_create() { + let ctx = crate::test_utils::dummy_context().await; let addr = "hello@mail.com"; let pub_key = crate::key::Key::from(alice_keypair().public); @@ -540,18 +548,18 @@ mod tests { }; assert!( - peerstate.save_to_db(&ctx.ctx.sql, true).is_ok(), + peerstate.save_to_db(&ctx.ctx.sql, true).await.is_ok(), "failed to save" ); assert!( - peerstate.save_to_db(&ctx.ctx.sql, true).is_ok(), + peerstate.save_to_db(&ctx.ctx.sql, true).await.is_ok(), "double-call with create failed" ); } - #[test] - fn test_peerstate_with_empty_gossip_key_save_to_db() { - let ctx = crate::test_utils::dummy_context(); + #[async_std::test] + async fn test_peerstate_with_empty_gossip_key_save_to_db() { + let ctx = crate::test_utils::dummy_context().await; let addr = "hello@mail.com"; let pub_key = crate::key::Key::from(alice_keypair().public); @@ -574,11 +582,12 @@ mod tests { }; assert!( - peerstate.save_to_db(&ctx.ctx.sql, true).is_ok(), + peerstate.save_to_db(&ctx.ctx.sql, true).await.is_ok(), "failed to save" ); let peerstate_new = Peerstate::from_addr(&ctx.ctx, &ctx.ctx.sql, addr) + .await .expect("failed to load peerstate from db"); // clear to_save, as that is not persissted diff --git a/src/qr.rs b/src/qr.rs index 8c69655df..59d00b8c9 100644 --- a/src/qr.rs +++ b/src/qr.rs @@ -50,13 +50,13 @@ pub async fn check_qr(context: &Context, qr: impl AsRef) -> Lot { } else if qr.starts_with(DCACCOUNT_SCHEME) { decode_account(context, qr) } else if qr.starts_with(MAILTO_SCHEME) { - decode_mailto(context, qr) + decode_mailto(context, qr).await } else if qr.starts_with(SMTP_SCHEME) { - decode_smtp(context, qr) + decode_smtp(context, qr).await } else if qr.starts_with(MATMSG_SCHEME) { - decode_matmsg(context, qr) + decode_matmsg(context, qr).await } else if qr.starts_with(VCARD_SCHEME) { - decode_vcard(context, qr) + decode_vcard(context, qr).await } else if qr.starts_with(HTTP_SCHEME) || qr.starts_with(HTTPS_SCHEME) { Lot::from_url(qr) } else { @@ -140,7 +140,7 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Lot { let mut lot = Lot::new(); // retrieve known state for this fingerprint - let peerstate = Peerstate::from_fingerprint(context, &context.sql, &fingerprint); + let peerstate = Peerstate::from_fingerprint(context, &context.sql, &fingerprint).await; if invitenumber.is_none() || auth.is_none() { if let Some(peerstate) = peerstate { @@ -160,7 +160,7 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Lot { .await .unwrap_or_default(); - chat::add_info_msg(context, id, format!("{} verified.", peerstate.addr)); + chat::add_info_msg(context, id, format!("{} verified.", peerstate.addr)).await; } else { lot.state = LotState::QrFprWithoutAddr; lot.text1 = Some(dc_format_fingerprint(&fingerprint)); @@ -262,7 +262,7 @@ pub async fn set_config_from_qr(context: &Context, qr: &str) -> Result<(), Error /// Extract address for the mailto scheme. /// /// Scheme: `mailto:addr...?subject=...&body=..` -fn decode_mailto(context: &Context, qr: &str) -> Lot { +async fn decode_mailto(context: &Context, qr: &str) -> Lot { let payload = &qr[MAILTO_SCHEME.len()..]; let addr = if let Some(query_index) = payload.find('?') { @@ -277,13 +277,13 @@ fn decode_mailto(context: &Context, qr: &str) -> Lot { }; let name = "".to_string(); - Lot::from_address(context, name, addr) + Lot::from_address(context, name, addr).await } /// Extract address for the smtp scheme. /// /// Scheme: `SMTP:addr...:subject...:body...` -fn decode_smtp(context: &Context, qr: &str) -> Lot { +async fn decode_smtp(context: &Context, qr: &str) -> Lot { let payload = &qr[SMTP_SCHEME.len()..]; let addr = if let Some(query_index) = payload.find(':') { @@ -298,7 +298,7 @@ fn decode_smtp(context: &Context, qr: &str) -> Lot { }; let name = "".to_string(); - Lot::from_address(context, name, addr) + Lot::from_address(context, name, addr).await } /// Extract address for the matmsg scheme. @@ -306,7 +306,7 @@ fn decode_smtp(context: &Context, qr: &str) -> Lot { /// Scheme: `MATMSG:TO:addr...;SUB:subject...;BODY:body...;` /// /// There may or may not be linebreaks after the fields. -fn decode_matmsg(context: &Context, qr: &str) -> Lot { +async fn decode_matmsg(context: &Context, qr: &str) -> Lot { // Does not work when the text `TO:` is used in subject/body _and_ TO: is not the first field. // we ignore this case. let addr = if let Some(to_index) = qr.find("TO:") { @@ -326,7 +326,7 @@ fn decode_matmsg(context: &Context, qr: &str) -> Lot { }; let name = "".to_string(); - Lot::from_address(context, name, addr) + Lot::from_address(context, name, addr).await } lazy_static! { @@ -339,7 +339,7 @@ lazy_static! { /// Extract address for the matmsg scheme. /// /// Scheme: `VCARD:BEGIN\nN:last name;first name;...;\nEMAIL;:addr...; -fn decode_vcard(context: &Context, qr: &str) -> Lot { +async fn decode_vcard(context: &Context, qr: &str) -> Lot { let name = VCARD_NAME_RE .captures(qr) .map(|caps| { @@ -359,7 +359,7 @@ fn decode_vcard(context: &Context, qr: &str) -> Lot { return format_err!("Bad e-mail address").into(); }; - Lot::from_address(context, name, addr) + Lot::from_address(context, name, addr).await } impl Lot { @@ -379,10 +379,10 @@ impl Lot { l } - pub fn from_address(context: &Context, name: String, addr: String) -> Self { + pub async fn from_address(context: &Context, name: String, addr: String) -> Self { let mut l = Lot::new(); l.state = LotState::QrAddr; - l.id = match Contact::add_or_lookup(context, name, addr, Origin::UnhandledQrScan) { + l.id = match Contact::add_or_lookup(context, name, addr, Origin::UnhandledQrScan).await { Ok((id, _)) => id, Err(err) => return err.into(), }; @@ -408,11 +408,11 @@ mod tests { use crate::test_utils::dummy_context; - #[test] - fn test_decode_http() { - let ctx = dummy_context(); + #[async_std::test] + async fn test_decode_http() { + let ctx = dummy_context().await; - let res = check_qr(&ctx.ctx, "http://www.hello.com"); + let res = check_qr(&ctx.ctx, "http://www.hello.com").await; assert_eq!(res.get_state(), LotState::QrUrl); assert_eq!(res.get_id(), 0); @@ -420,11 +420,11 @@ mod tests { assert!(res.get_text2().is_none()); } - #[test] - fn test_decode_https() { - let ctx = dummy_context(); + #[async_std::test] + async fn test_decode_https() { + let ctx = dummy_context().await; - let res = check_qr(&ctx.ctx, "https://www.hello.com"); + let res = check_qr(&ctx.ctx, "https://www.hello.com").await; assert_eq!(res.get_state(), LotState::QrUrl); assert_eq!(res.get_id(), 0); @@ -432,11 +432,11 @@ mod tests { assert!(res.get_text2().is_none()); } - #[test] - fn test_decode_text() { - let ctx = dummy_context(); + #[async_std::test] + async fn test_decode_text() { + let ctx = dummy_context().await; - let res = check_qr(&ctx.ctx, "I am so cool"); + let res = check_qr(&ctx.ctx, "I am so cool").await; assert_eq!(res.get_state(), LotState::QrText); assert_eq!(res.get_id(), 0); @@ -444,124 +444,127 @@ mod tests { assert!(res.get_text2().is_none()); } - #[test] - fn test_decode_vcard() { - let ctx = dummy_context(); + #[async_std::test] + async fn test_decode_vcard() { + let ctx = dummy_context().await; let res = check_qr( &ctx.ctx, "BEGIN:VCARD\nVERSION:3.0\nN:Last;First\nEMAIL;TYPE=INTERNET:stress@test.local\nEND:VCARD" - ); + ).await; println!("{:?}", res); assert_eq!(res.get_state(), LotState::QrAddr); assert_ne!(res.get_id(), 0); - let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).unwrap(); + let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).await.unwrap(); assert_eq!(contact.get_addr(), "stress@test.local"); assert_eq!(contact.get_name(), "First Last"); } - #[test] - fn test_decode_matmsg() { - let ctx = dummy_context(); + #[async_std::test] + async fn test_decode_matmsg() { + let ctx = dummy_context().await; let res = check_qr( &ctx.ctx, "MATMSG:TO:\n\nstress@test.local ; \n\nSUB:\n\nSubject here\n\nBODY:\n\nhelloworld\n;;", - ); + ) + .await; println!("{:?}", res); assert_eq!(res.get_state(), LotState::QrAddr); assert_ne!(res.get_id(), 0); - let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).unwrap(); + let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).await.unwrap(); assert_eq!(contact.get_addr(), "stress@test.local"); } - #[test] - fn test_decode_mailto() { - let ctx = dummy_context(); + #[async_std::test] + async fn test_decode_mailto() { + let ctx = dummy_context().await; let res = check_qr( &ctx.ctx, "mailto:stress@test.local?subject=hello&body=world", - ); + ) + .await; println!("{:?}", res); assert_eq!(res.get_state(), LotState::QrAddr); assert_ne!(res.get_id(), 0); - let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).unwrap(); + let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).await.unwrap(); assert_eq!(contact.get_addr(), "stress@test.local"); - let res = check_qr(&ctx.ctx, "mailto:no-questionmark@example.org"); + let res = check_qr(&ctx.ctx, "mailto:no-questionmark@example.org").await; assert_eq!(res.get_state(), LotState::QrAddr); assert_ne!(res.get_id(), 0); - let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).unwrap(); + let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).await.unwrap(); assert_eq!(contact.get_addr(), "no-questionmark@example.org"); - let res = check_qr(&ctx.ctx, "mailto:no-addr"); + let res = check_qr(&ctx.ctx, "mailto:no-addr").await; assert_eq!(res.get_state(), LotState::QrError); assert!(res.get_text1().is_some()); } - #[test] - fn test_decode_smtp() { - let ctx = dummy_context(); + #[async_std::test] + async fn test_decode_smtp() { + let ctx = dummy_context().await; - let res = check_qr(&ctx.ctx, "SMTP:stress@test.local:subjecthello:bodyworld"); + let res = check_qr(&ctx.ctx, "SMTP:stress@test.local:subjecthello:bodyworld").await; println!("{:?}", res); assert_eq!(res.get_state(), LotState::QrAddr); assert_ne!(res.get_id(), 0); - let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).unwrap(); + let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).await.unwrap(); assert_eq!(contact.get_addr(), "stress@test.local"); } - #[test] - fn test_decode_openpgp_group() { - let ctx = dummy_context(); + #[async_std::test] + async fn test_decode_openpgp_group() { + let ctx = dummy_context().await; let res = check_qr( &ctx.ctx, "OPENPGP4FPR:79252762C34C5096AF57958F4FC3D21A81B0F0A7#a=cli%40deltachat.de&g=test%20%3F+test%20%21&x=h-0oKQf2CDK&i=9JEXlxAqGM0&s=0V7LzL9cxRL" - ); + ).await; println!("{:?}", res); assert_eq!(res.get_state(), LotState::QrAskVerifyGroup); assert_ne!(res.get_id(), 0); assert_eq!(res.get_text1().unwrap(), "test ? test !"); - let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).unwrap(); + let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).await.unwrap(); assert_eq!(contact.get_addr(), "cli@deltachat.de"); } - #[test] - fn test_decode_openpgp_secure_join() { - let ctx = dummy_context(); + #[async_std::test] + async fn test_decode_openpgp_secure_join() { + let ctx = dummy_context().await; let res = check_qr( &ctx.ctx, "OPENPGP4FPR:79252762C34C5096AF57958F4FC3D21A81B0F0A7#a=cli%40deltachat.de&n=J%C3%B6rn%20P.+P.&i=TbnwJ6lSvD5&s=0ejvbdFSQxB" - ); + ).await; println!("{:?}", res); assert_eq!(res.get_state(), LotState::QrAskVerifyContact); assert_ne!(res.get_id(), 0); - let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).unwrap(); + let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).await.unwrap(); assert_eq!(contact.get_addr(), "cli@deltachat.de"); assert_eq!(contact.get_name(), "Jörn P. P."); } - #[test] - fn test_decode_openpgp_without_addr() { - let ctx = dummy_context(); + #[async_std::test] + async fn test_decode_openpgp_without_addr() { + let ctx = dummy_context().await; let res = check_qr( &ctx.ctx, "OPENPGP4FPR:1234567890123456789012345678901234567890", - ); + ) + .await; assert_eq!(res.get_state(), LotState::QrFprWithoutAddr); assert_eq!( res.get_text1().unwrap(), @@ -569,31 +572,32 @@ mod tests { ); assert_eq!(res.get_id(), 0); - let res = check_qr(&ctx.ctx, "OPENPGP4FPR:12345678901234567890"); + let res = check_qr(&ctx.ctx, "OPENPGP4FPR:12345678901234567890").await; assert_eq!(res.get_state(), LotState::QrError); assert_eq!(res.get_id(), 0); } - #[test] - fn test_decode_account() { - let ctx = dummy_context(); + #[async_std::test] + async fn test_decode_account() { + let ctx = dummy_context().await; let res = check_qr( &ctx.ctx, "DCACCOUNT:https://example.org/new_email?t=1w_7wDjgjelxeX884x96v3", - ); + ) + .await; assert_eq!(res.get_state(), LotState::QrAccount); assert_eq!(res.get_text1().unwrap(), "example.org"); } - #[test] - fn test_decode_account_bad_scheme() { - let ctx = dummy_context(); - + #[async_std::test] + async fn test_decode_account_bad_scheme() { + let ctx = dummy_context().await; let res = check_qr( &ctx.ctx, "DCACCOUNT:http://example.org/new_email?t=1w_7wDjgjelxeX884x96v3", - ); + ) + .await; assert_eq!(res.get_state(), LotState::QrError); assert!(res.get_text1().is_some()); } diff --git a/src/securejoin.rs b/src/securejoin.rs index feda5657f..6998c4f7b 100644 --- a/src/securejoin.rs +++ b/src/securejoin.rs @@ -55,7 +55,7 @@ macro_rules! get_qr_attr { $context .bob .read() - .unwrap() + .await .qr_scan .as_ref() .unwrap() @@ -65,7 +65,7 @@ macro_rules! get_qr_attr { }; } -pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: ChatId) -> Option { +pub async fn dc_get_securejoin_qr(context: &Context, group_chat_id: ChatId) -> Option { /*======================================================= ==== Alice - the inviter side ==== ==== Step 1 in "Setup verified contact" protocol ==== @@ -73,13 +73,14 @@ pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: ChatId) -> Option< let fingerprint: String; - ensure_secret_key_exists(context).ok(); + ensure_secret_key_exists(context).await.ok(); // invitenumber will be used to allow starting the handshake, // auth will be used to verify the fingerprint - let invitenumber = token::lookup_or_new(context, token::Namespace::InviteNumber, group_chat_id); - let auth = token::lookup_or_new(context, token::Namespace::Auth, group_chat_id); - let self_addr = match context.get_config(Config::ConfiguredAddr) { + let invitenumber = + token::lookup_or_new(context, token::Namespace::InviteNumber, group_chat_id).await; + let auth = token::lookup_or_new(context, token::Namespace::Auth, group_chat_id).await; + let self_addr = match context.get_config(Config::ConfiguredAddr).await { Some(addr) => addr, None => { error!(context, "Not configured, cannot generate QR code.",); @@ -87,9 +88,12 @@ pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: ChatId) -> Option< } }; - let self_name = context.get_config(Config::Displayname).unwrap_or_default(); + let self_name = context + .get_config(Config::Displayname) + .await + .unwrap_or_default(); - fingerprint = match get_self_fingerprint(context) { + fingerprint = match get_self_fingerprint(context).await { Some(fp) => fp, None => { return None; @@ -103,7 +107,7 @@ pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: ChatId) -> Option< let qr = if !group_chat_id.is_unset() { // parameters used: a=g=x=i=s= - if let Ok(chat) = Chat::load_from_db(context, group_chat_id) { + if let Ok(chat) = Chat::load_from_db(context, group_chat_id).await { let group_name = chat.get_name(); let group_name_urlencoded = utf8_percent_encode(&group_name, NON_ALPHANUMERIC).to_string(); @@ -134,9 +138,9 @@ pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: ChatId) -> Option< qr } -fn get_self_fingerprint(context: &Context) -> Option { - if let Some(self_addr) = context.get_config(Config::ConfiguredAddr) { - if let Some(key) = Key::from_self_public(context, self_addr, &context.sql) { +async fn get_self_fingerprint(context: &Context) -> Option { + if let Some(self_addr) = context.get_config(Config::ConfiguredAddr).await { + if let Some(key) = Key::from_self_public(context, self_addr, &context.sql).await { return Some(key.fingerprint()); } } @@ -149,7 +153,7 @@ async fn cleanup( ongoing_allocated: bool, join_vg: bool, ) -> ChatId { - let mut bob = context.bob.write().unwrap(); + let mut bob = context.bob.write().await; bob.expects = 0; let ret_chat_id: ChatId = if bob.status == DC_BOB_SUCCESS { if join_vg { @@ -169,7 +173,7 @@ async fn cleanup( bob.qr_scan = None; if ongoing_allocated { - context.free_ongoing(); + context.free_ongoing().await; } ret_chat_id } @@ -187,7 +191,7 @@ pub async fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId { info!(context, "Requesting secure-join ...",); ensure_secret_key_exists(context).await.ok(); - if !context.alloc_ongoing() { + if !context.alloc_ongoing().await { return cleanup(&context, contact_chat_id, false, join_vg).await; } let qr_scan = check_qr(context, &qr).await; @@ -203,12 +207,12 @@ pub async fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId { return cleanup(&context, contact_chat_id, true, join_vg).await; } }; - if context.shall_stop_ongoing() { + if context.shall_stop_ongoing().await { return cleanup(&context, contact_chat_id, true, join_vg).await; } join_vg = qr_scan.get_state() == LotState::QrAskVerifyGroup; { - let mut bob = context.bob.write().unwrap(); + let mut bob = context.bob.write().await; bob.status = 0; bob.qr_scan = Some(qr_scan); } @@ -217,7 +221,7 @@ pub async fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId { context .bob .read() - .unwrap() + .await .qr_scan .as_ref() .unwrap() @@ -225,13 +229,19 @@ pub async fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId { .as_ref() .unwrap(), contact_chat_id, - ) { + ) + .await + { // the scanned fingerprint matches Alice's key, // we can proceed to step 4b) directly and save two mails info!(context, "Taking protocol shortcut."); - context.bob.write().unwrap().expects = DC_VC_CONTACT_CONFIRM; - joiner_progress!(context, chat_id_2_contact_id(context, contact_chat_id), 400); - let own_fingerprint = get_self_fingerprint(context).unwrap_or_default(); + context.bob.write().await.expects = DC_VC_CONTACT_CONFIRM; + joiner_progress!( + context, + chat_id_2_contact_id(context, contact_chat_id).await, + 400 + ); + let own_fingerprint = get_self_fingerprint(context).await.unwrap_or_default(); // Bob -> Alice send_handshake_msg( @@ -252,7 +262,7 @@ pub async fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId { ) .await; } else { - context.bob.write().unwrap().expects = DC_VC_AUTH_REQUIRED; + context.bob.write().await.expects = DC_VC_AUTH_REQUIRED; // Bob -> Alice send_handshake_msg( @@ -268,14 +278,14 @@ pub async fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId { if join_vg { // for a group-join, wait until the secure-join is done and the group is created - while !context.shall_stop_ongoing() { + while !context.shall_stop_ongoing().await { std::thread::sleep(std::time::Duration::from_millis(200)); } cleanup(&context, contact_chat_id, true, join_vg).await } else { // for a one-to-one-chat, the chat is already known, return the chat-id, // the verification runs in background - context.free_ongoing(); + context.free_ongoing().await; contact_chat_id } } @@ -321,8 +331,8 @@ async fn send_handshake_msg( .unwrap_or_default(); } -fn chat_id_2_contact_id(context: &Context, contact_chat_id: ChatId) -> u32 { - let contacts = chat::get_chat_contacts(context, contact_chat_id); +async fn chat_id_2_contact_id(context: &Context, contact_chat_id: ChatId) -> u32 { + let contacts = chat::get_chat_contacts(context, contact_chat_id).await; if contacts.len() == 1 { contacts[0] } else { @@ -330,16 +340,17 @@ fn chat_id_2_contact_id(context: &Context, contact_chat_id: ChatId) -> u32 { } } -fn fingerprint_equals_sender( +async fn fingerprint_equals_sender( context: &Context, fingerprint: impl AsRef, contact_chat_id: ChatId, ) -> bool { - let contacts = chat::get_chat_contacts(context, contact_chat_id); + let contacts = chat::get_chat_contacts(context, contact_chat_id).await; if contacts.len() == 1 { - if let Ok(contact) = Contact::load_from_db(context, contacts[0]) { - if let Some(peerstate) = Peerstate::from_addr(context, &context.sql, contact.get_addr()) + if let Ok(contact) = Contact::load_from_db(context, contacts[0]).await { + if let Some(peerstate) = + Peerstate::from_addr(context, &context.sql, contact.get_addr()).await { let fingerprint_normalized = dc_normalize_fingerprint(fingerprint.as_ref()); if peerstate.public_key_fingerprint.is_some() @@ -420,7 +431,7 @@ pub(crate) async fn handle_securejoin_handshake( match chat::create_or_lookup_by_contact_id(context, contact_id, Blocked::Not).await { Ok((chat_id, blocked)) => { if blocked != Blocked::Not { - chat_id.unblock(context); + chat_id.unblock(context).await; } chat_id } @@ -480,7 +491,7 @@ pub(crate) async fn handle_securejoin_handshake( // verify that Alice's Autocrypt key and fingerprint matches the QR-code let cond = { - let bob = context.bob.read().unwrap(); + let bob = context.bob.read().await; let scan = bob.qr_scan.as_ref(); scan.is_none() || bob.expects != DC_VC_AUTH_REQUIRED @@ -504,25 +515,29 @@ pub(crate) async fn handle_securejoin_handshake( } else { "Not encrypted." }, - ); - context.bob.write().unwrap().status = 0; // secure-join failed - context.stop_ongoing(); + ) + .await; + context.bob.write().await.status = 0; // secure-join failed + context.stop_ongoing().await; return Ok(HandshakeMessage::Ignore); } - if !fingerprint_equals_sender(context, &scanned_fingerprint_of_alice, contact_chat_id) { + if !fingerprint_equals_sender(context, &scanned_fingerprint_of_alice, contact_chat_id) + .await + { could_not_establish_secure_connection( context, contact_chat_id, "Fingerprint mismatch on joiner-side.", - ); - context.bob.write().unwrap().status = 0; // secure-join failed - context.stop_ongoing(); + ) + .await; + context.bob.write().await.status = 0; // secure-join failed + context.stop_ongoing().await; return Ok(HandshakeMessage::Ignore); } info!(context, "Fingerprint verified.",); - own_fingerprint = get_self_fingerprint(context).unwrap(); + own_fingerprint = get_self_fingerprint(context).await.unwrap(); joiner_progress!(context, contact_id, 400); - context.bob.write().unwrap().expects = DC_VC_CONTACT_CONFIRM; + context.bob.write().await.expects = DC_VC_CONTACT_CONFIRM; // Bob -> Alice send_handshake_msg( @@ -555,7 +570,8 @@ pub(crate) async fn handle_securejoin_handshake( context, contact_chat_id, "Fingerprint not provided.", - ); + ) + .await; return Ok(HandshakeMessage::Ignore); } }; @@ -564,15 +580,17 @@ pub(crate) async fn handle_securejoin_handshake( context, contact_chat_id, "Auth not encrypted.", - ); + ) + .await; return Ok(HandshakeMessage::Ignore); } - if !fingerprint_equals_sender(context, &fingerprint, contact_chat_id) { + if !fingerprint_equals_sender(context, &fingerprint, contact_chat_id).await { could_not_establish_secure_connection( context, contact_chat_id, "Fingerprint mismatch on inviter-side.", - ); + ) + .await; return Ok(HandshakeMessage::Ignore); } info!(context, "Fingerprint verified.",); @@ -584,25 +602,28 @@ pub(crate) async fn handle_securejoin_handshake( context, contact_chat_id, "Auth not provided.", - ); + ) + .await; return Ok(HandshakeMessage::Ignore); } }; if !token::exists(context, token::Namespace::Auth, &auth_0).await { - could_not_establish_secure_connection(context, contact_chat_id, "Auth invalid."); + could_not_establish_secure_connection(context, contact_chat_id, "Auth invalid.") + .await; return Ok(HandshakeMessage::Ignore); } - if mark_peer_as_verified(context, fingerprint).is_err() { + if mark_peer_as_verified(context, fingerprint).await.is_err() { could_not_establish_secure_connection( context, contact_chat_id, "Fingerprint mismatch on inviter-side.", - ); + ) + .await; return Ok(HandshakeMessage::Ignore); } - Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinInvited); + Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinInvited).await; info!(context, "Auth verified.",); - secure_connection_established(context, contact_chat_id); + secure_connection_established(context, contact_chat_id).await; emit_event!(context, Event::ContactsChanged(Some(contact_id))); inviter_progress!(context, contact_id, 600); if join_vg { @@ -651,12 +672,12 @@ pub(crate) async fn handle_securejoin_handshake( HandshakeMessage::Ignore }; - if context.bob.read().unwrap().expects != DC_VC_CONTACT_CONFIRM { + if context.bob.read().await.expects != DC_VC_CONTACT_CONFIRM { info!(context, "Message belongs to a different handshake.",); return Ok(abort_retval); } let cond = { - let bob = context.bob.read().unwrap(); + let bob = context.bob.read().await; let scan = bob.qr_scan.as_ref(); scan.is_none() || (join_vg && scan.unwrap().state != LotState::QrAskVerifyGroup) }; @@ -695,20 +716,25 @@ pub(crate) async fn handle_securejoin_handshake( context, contact_chat_id, "Contact confirm message not encrypted.", - ); - context.bob.write().unwrap().status = 0; + ) + .await; + context.bob.write().await.status = 0; return Ok(abort_retval); } - if mark_peer_as_verified(context, &scanned_fingerprint_of_alice).is_err() { + if mark_peer_as_verified(context, &scanned_fingerprint_of_alice) + .await + .is_err() + { could_not_establish_secure_connection( context, contact_chat_id, "Fingerprint mismatch on joiner-side.", - ); + ) + .await; return Ok(abort_retval); } - Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinJoined); + Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinJoined).await; emit_event!(context, Event::ContactsChanged(None)); let cg_member_added = mime_message .get(HeaderDef::ChatGroupMemberAdded) @@ -723,8 +749,8 @@ pub(crate) async fn handle_securejoin_handshake( info!(context, "Message belongs to a different handshake (scaled up contact anyway to allow creation of group)."); return Ok(abort_retval); } - secure_connection_established(context, contact_chat_id); - context.bob.write().unwrap().expects = 0; + secure_connection_established(context, contact_chat_id).await; + context.bob.write().await.expects = 0; if join_vg { // Bob -> Alice send_handshake_msg( @@ -737,8 +763,8 @@ pub(crate) async fn handle_securejoin_handshake( ) .await; } - context.bob.write().unwrap().status = 1; - context.stop_ongoing(); + context.bob.write().await.status = 1; + context.stop_ongoing().await; Ok(if join_vg { HandshakeMessage::Propagate } else { @@ -752,7 +778,7 @@ pub(crate) async fn handle_securejoin_handshake( ==========================================================*/ if let Ok(contact) = Contact::get_by_id(context, contact_id).await { - if contact.is_verified(context) == VerifiedStatus::Unverified { + if contact.is_verified(context).await == VerifiedStatus::Unverified { warn!(context, "vg-member-added-received invalid.",); return Ok(HandshakeMessage::Ignore); } @@ -787,42 +813,49 @@ pub(crate) async fn handle_securejoin_handshake( } } -fn secure_connection_established(context: &Context, contact_chat_id: ChatId) { - let contact_id: u32 = chat_id_2_contact_id(context, contact_chat_id); - let contact = Contact::get_by_id(context, contact_id); +async fn secure_connection_established(context: &Context, contact_chat_id: ChatId) { + let contact_id: u32 = chat_id_2_contact_id(context, contact_chat_id).await; + let contact = Contact::get_by_id(context, contact_id).await; let addr = if let Ok(ref contact) = contact { contact.get_addr() } else { "?" }; - let msg = context.stock_string_repl_str(StockMessage::ContactVerified, addr); - chat::add_info_msg(context, contact_chat_id, msg); + let msg = context + .stock_string_repl_str(StockMessage::ContactVerified, addr) + .await; + chat::add_info_msg(context, contact_chat_id, msg).await; emit_event!(context, Event::ChatModified(contact_chat_id)); } -fn could_not_establish_secure_connection( +async fn could_not_establish_secure_connection( context: &Context, contact_chat_id: ChatId, details: &str, ) { - let contact_id = chat_id_2_contact_id(context, contact_chat_id); - let contact = Contact::get_by_id(context, contact_id); - let msg = context.stock_string_repl_str( - StockMessage::ContactNotVerified, - if let Ok(ref contact) = contact { - contact.get_addr() - } else { - "?" - }, - ); + let contact_id = chat_id_2_contact_id(context, contact_chat_id).await; + let contact = Contact::get_by_id(context, contact_id).await; + let msg = context + .stock_string_repl_str( + StockMessage::ContactNotVerified, + if let Ok(ref contact) = contact { + contact.get_addr() + } else { + "?" + }, + ) + .await; - chat::add_info_msg(context, contact_chat_id, &msg); + chat::add_info_msg(context, contact_chat_id, &msg).await; error!(context, "{} ({})", &msg, details); } -fn mark_peer_as_verified(context: &Context, fingerprint: impl AsRef) -> Result<(), Error> { +async fn mark_peer_as_verified( + context: &Context, + fingerprint: impl AsRef, +) -> Result<(), Error> { if let Some(ref mut peerstate) = - Peerstate::from_fingerprint(context, &context.sql, fingerprint.as_ref()) + Peerstate::from_fingerprint(context, &context.sql, fingerprint.as_ref()).await { if peerstate.set_verified( PeerstateKeyType::PublicKey, @@ -833,6 +866,7 @@ fn mark_peer_as_verified(context: &Context, fingerprint: impl AsRef) -> Res peerstate.to_save = Some(ToSave::All); peerstate .save_to_db(&context.sql, false) + .await .unwrap_or_default(); return Ok(()); } @@ -891,7 +925,7 @@ pub async fn handle_degrade_event( .query_get_value( context, "SELECT id FROM contacts WHERE addr=?;", - params![&peerstate.addr], + paramsv![peerstate.addr], ) .await { @@ -908,9 +942,10 @@ pub async fn handle_degrade_event( .unwrap_or_default(); let msg = context - .stock_string_repl_str(StockMessage::ContactSetupChanged, peerstate.addr.clone()); + .stock_string_repl_str(StockMessage::ContactSetupChanged, peerstate.addr.clone()) + .await; - chat::add_info_msg(context, contact_chat_id, msg); + chat::add_info_msg(context, contact_chat_id, msg).await; emit_event!(context, Event::ChatModified(contact_chat_id)); } } diff --git a/src/smtp/mod.rs b/src/smtp/mod.rs index bcb98a25f..0734c717b 100644 --- a/src/smtp/mod.rs +++ b/src/smtp/mod.rs @@ -159,7 +159,7 @@ impl Smtp { // oauth2 let addr = &lp.addr; let send_pw = &lp.send_pw; - let access_token = dc_get_oauth2_access_token(context, addr, send_pw, false); + let access_token = dc_get_oauth2_access_token(context, addr, send_pw, false).await; if access_token.is_none() { return Err(Error::Oauth2Error { address: addr.to_string(), diff --git a/src/sql.rs b/src/sql.rs index f5a08f1d3..30abf1da4 100644 --- a/src/sql.rs +++ b/src/sql.rs @@ -7,7 +7,7 @@ use std::collections::HashSet; use std::path::Path; use std::time::Duration; -use rusqlite::{Connection, Error as SqlError, OpenFlags, NO_PARAMS}; +use rusqlite::{Connection, Error as SqlError, OpenFlags}; use thread_local_object::ThreadLocal; use crate::chat::{update_device_icon, update_saved_messages_icon}; @@ -107,34 +107,34 @@ impl Sql { } } - pub async fn execute>(&self, sql: S, params: P) -> Result - where - P: IntoIterator, - P::Item: rusqlite::ToSql, - { + pub async fn execute>( + &self, + sql: S, + params: Vec<&dyn crate::ToSql>, + ) -> Result { self.start_stmt(sql.as_ref()); let res = { let conn = self.get_conn().await?; - conn.execute(sql.as_ref(), params)? + let res = conn.execute(sql.as_ref(), params); + self.in_use.remove(); + res }; - Ok(res) + res.map_err(Into::into) } /// Prepares and executes the statement and maps a function over the resulting rows. /// Then executes the second function over the returned iterator and returns the /// result of that function. - pub async fn query_map( + pub async fn query_map( &self, sql: impl AsRef, - params: P, + params: Vec<&dyn crate::ToSql>, f: F, mut g: G, ) -> Result where - P: IntoIterator, - P::Item: rusqlite::ToSql, F: FnMut(&rusqlite::Row) -> rusqlite::Result, G: FnMut(rusqlite::MappedRows) -> Result, { @@ -144,41 +144,12 @@ impl Sql { let res = { let conn = self.get_conn().await?; let mut stmt = conn.prepare(sql)?; - let res = stmt.query_map(params, f)?; - g(res)? + let res = stmt.query_map(¶ms, f)?; + self.in_use.remove(); + g(res) }; - Ok(res) - } - - /// Prepares and executes the statement and maps a function over the resulting rows. - /// Then executes the second function over the returned iterator and returns the - /// result of that function. - pub async fn query_map_async<'a, T, P, F, G, H, Fut>( - &self, - sql: impl AsRef, - params: P, - f: F, - g: G, - ) -> Result - where - P: IntoIterator, - P::Item: rusqlite::ToSql, - F: FnMut(&rusqlite::Row) -> rusqlite::Result, - G: FnMut(rusqlite::MappedRows<'a, F>) -> Fut, - Fut: Future> + 'a, - { - self.start_stmt(sql.as_ref().to_string()); - unimplemented!() - // let sql = sql.as_ref(); - // self.with_conn_async(|conn| async move { - // let mut stmt = conn.prepare(sql)?; - // { - // let res = stmt.query_map(params, f)?; - // g(res).await - // } - // }) - // .await + res } pub async fn get_conn( @@ -226,36 +197,39 @@ impl Sql { /// Return `true` if a query in the SQL statement it executes returns one or more /// rows and false if the SQL returns an empty set. - pub async fn exists

(&self, sql: &str, params: P) -> Result - where - P: IntoIterator, - P::Item: rusqlite::ToSql, - { + pub async fn exists(&self, sql: &str, params: Vec<&dyn crate::ToSql>) -> Result { self.start_stmt(sql.to_string()); let res = { let conn = self.get_conn().await?; let mut stmt = conn.prepare(sql)?; - stmt.exists(params)? + let res = stmt.exists(¶ms); + self.in_use.remove(); + res }; - Ok(res) + res.map_err(Into::into) } /// Execute a query which is expected to return one row. - pub async fn query_row(&self, sql: impl AsRef, params: P, f: F) -> Result + pub async fn query_row( + &self, + sql: impl AsRef, + params: Vec<&dyn crate::ToSql>, + f: F, + ) -> Result where - P: IntoIterator + Copy, - P::Item: rusqlite::ToSql, F: FnOnce(&rusqlite::Row) -> rusqlite::Result, { self.start_stmt(sql.as_ref().to_string()); let sql = sql.as_ref(); let res = { let conn = self.get_conn().await?; - conn.query_row(sql, params, f)? + let res = conn.query_row(sql, params, f); + self.in_use.remove(); + res }; - Ok(res) + res.map_err(Into::into) } pub async fn table_exists(&self, name: impl AsRef) -> Result { @@ -276,10 +250,12 @@ impl Sql { /// Executes a query which is expected to return one row and one /// column. If the query does not return a value or returns SQL /// `NULL`, returns `Ok(None)`. - pub async fn query_get_value_result(&self, query: &str, params: P) -> Result> + pub async fn query_get_value_result( + &self, + query: &str, + params: Vec<&dyn crate::ToSql>, + ) -> Result> where - P: IntoIterator + Copy, - P::Item: rusqlite::ToSql, T: rusqlite::types::FromSql, { match self @@ -299,15 +275,13 @@ impl Sql { /// Not resultified version of `query_get_value_result`. Returns /// `None` on error. - pub async fn query_get_value( + pub async fn query_get_value( &self, context: &Context, query: &str, - params: P, + params: Vec<&dyn crate::ToSql>, ) -> Option where - P: IntoIterator + Copy, - P::Item: rusqlite::ToSql, T: rusqlite::types::FromSql, { match self.query_get_value_result(query, params).await { @@ -337,23 +311,23 @@ impl Sql { let key = key.as_ref(); let res = if let Some(ref value) = value { let exists = self - .exists("SELECT value FROM config WHERE keyname=?;", params![key]) + .exists("SELECT value FROM config WHERE keyname=?;", paramsv![key]) .await?; if exists { self.execute( "UPDATE config SET value=? WHERE keyname=?;", - params![value, key], + paramsv![value.to_string(), key.to_string()], ) .await } else { self.execute( "INSERT INTO config (keyname, value) VALUES (?, ?);", - params![key, value], + paramsv![key.to_string(), value.to_string()], ) .await } } else { - self.execute("DELETE FROM config WHERE keyname=?;", params![key]) + self.execute("DELETE FROM config WHERE keyname=?;", paramsv![key]) .await }; @@ -374,7 +348,7 @@ impl Sql { self.query_get_value( context, "SELECT value FROM config WHERE keyname=?;", - params![key.as_ref()], + paramsv![key.as_ref().to_string()], ) .await } @@ -455,18 +429,14 @@ impl Sql { ) -> Result { self.start_stmt("get rowid".to_string()); - let query = format!( - "SELECT id FROM {} WHERE {}=? ORDER BY id DESC", - table.as_ref(), - field.as_ref(), - ); - let res = { let mut conn = self.get_conn().await?; - get_rowid(context, &mut conn, table, field, value)? + let res = get_rowid(context, &mut conn, table, field, value); + self.in_use.remove(); + res }; - Ok(res) + res.map_err(Into::into) } pub async fn get_rowid2( @@ -482,15 +452,17 @@ impl Sql { let res = { let mut conn = self.get_conn().await?; - get_rowid2(context, &mut conn, table, field, value, field2, value2)? + let res = get_rowid2(context, &mut conn, table, field, value, field2, value2); + self.in_use.remove(); + res }; - Ok(res) + res.map_err(Into::into) } } pub fn get_rowid( - context: &Context, + _context: &Context, conn: &mut Connection, table: impl AsRef, field: impl AsRef, @@ -509,7 +481,7 @@ pub fn get_rowid( } pub fn get_rowid2( - context: &Context, + _context: &Context, conn: &mut Connection, table: impl AsRef, field: impl AsRef, @@ -526,7 +498,7 @@ pub fn get_rowid2( field2.as_ref(), value2, ), - NO_PARAMS, + params![], |row| row.get::<_, u32>(0), ) } @@ -569,7 +541,7 @@ pub async fn housekeeping(context: &Context) { .sql .query_map( "SELECT value FROM config;", - params![], + paramsv![], |row| row.get::<_, String>(0), |rows| { for row in rows { @@ -683,7 +655,7 @@ async fn maybe_add_from_param( .sql .query_map( query, - NO_PARAMS, + paramsv![], |row| row.get::<_, String>(0), |rows| { for row in rows { @@ -762,11 +734,14 @@ async fn open( ); sql.execute( "CREATE TABLE config (id INTEGER PRIMARY KEY, keyname TEXT, value TEXT);", - NO_PARAMS, + paramsv![], + ) + .await?; + sql.execute( + "CREATE INDEX config_index1 ON config (keyname);", + paramsv![], ) .await?; - sql.execute("CREATE INDEX config_index1 ON config (keyname);", NO_PARAMS) - .await?; sql.execute( "CREATE TABLE contacts (\ id INTEGER PRIMARY KEY AUTOINCREMENT, \ @@ -776,17 +751,17 @@ async fn open( blocked INTEGER DEFAULT 0, \ last_seen INTEGER DEFAULT 0, \ param TEXT DEFAULT '');", - params![], + paramsv![], ) .await?; sql.execute( "CREATE INDEX contacts_index1 ON contacts (name COLLATE NOCASE);", - params![], + paramsv![], ) .await?; sql.execute( "CREATE INDEX contacts_index2 ON contacts (addr COLLATE NOCASE);", - params![], + paramsv![], ) .await?; sql.execute( @@ -794,7 +769,7 @@ async fn open( (1,'self',262144), (2,'info',262144), (3,'rsvd',262144), \ (4,'rsvd',262144), (5,'device',262144), (6,'rsvd',262144), \ (7,'rsvd',262144), (8,'rsvd',262144), (9,'rsvd',262144);", - params![], + paramsv![], ) .await?; sql.execute( @@ -807,19 +782,19 @@ async fn open( blocked INTEGER DEFAULT 0, \ grpid TEXT DEFAULT '', \ param TEXT DEFAULT '');", - params![], + paramsv![], ) .await?; - sql.execute("CREATE INDEX chats_index1 ON chats (grpid);", params![]) + sql.execute("CREATE INDEX chats_index1 ON chats (grpid);", paramsv![]) .await?; sql.execute( "CREATE TABLE chats_contacts (chat_id INTEGER, contact_id INTEGER);", - params![], + paramsv![], ) .await?; sql.execute( "CREATE INDEX chats_contacts_index1 ON chats_contacts (chat_id);", - params![], + paramsv![], ) .await?; sql.execute( @@ -827,7 +802,7 @@ async fn open( (1,120,'deaddrop'), (2,120,'rsvd'), (3,120,'trash'), \ (4,120,'msgs_in_creation'), (5,120,'starred'), (6,120,'archivedlink'), \ (7,100,'rsvd'), (8,100,'rsvd'), (9,100,'rsvd');", - params![], + paramsv![], ) .await?; sql.execute( @@ -847,23 +822,23 @@ async fn open( txt TEXT DEFAULT '', \ txt_raw TEXT DEFAULT '', \ param TEXT DEFAULT '');", - params![], + paramsv![], ) .await?; - sql.execute("CREATE INDEX msgs_index1 ON msgs (rfc724_mid);", params![]) + sql.execute("CREATE INDEX msgs_index1 ON msgs (rfc724_mid);", paramsv![]) .await?; - sql.execute("CREATE INDEX msgs_index2 ON msgs (chat_id);", params![]) + sql.execute("CREATE INDEX msgs_index2 ON msgs (chat_id);", paramsv![]) .await?; - sql.execute("CREATE INDEX msgs_index3 ON msgs (timestamp);", params![]) + sql.execute("CREATE INDEX msgs_index3 ON msgs (timestamp);", paramsv![]) .await?; - sql.execute("CREATE INDEX msgs_index4 ON msgs (state);", params![]) + sql.execute("CREATE INDEX msgs_index4 ON msgs (state);", paramsv![]) .await?; sql.execute( "INSERT INTO msgs (id,msgrmsg,txt) VALUES \ (1,0,'marker1'), (2,0,'rsvd'), (3,0,'rsvd'), \ (4,0,'rsvd'), (5,0,'rsvd'), (6,0,'rsvd'), (7,0,'rsvd'), \ (8,0,'rsvd'), (9,0,'daymarker');", - params![], + paramsv![], ) .await?; sql.execute( @@ -874,12 +849,12 @@ async fn open( action INTEGER, \ foreign_id INTEGER, \ param TEXT DEFAULT '');", - params![], + paramsv![], ) .await?; sql.execute( "CREATE INDEX jobs_index1 ON jobs (desired_timestamp);", - params![], + paramsv![], ) .await?; if !sql.table_exists("config").await? @@ -920,12 +895,12 @@ async fn open( info!(context, "[migration] v1"); sql.execute( "CREATE TABLE leftgrps ( id INTEGER PRIMARY KEY, grpid TEXT DEFAULT '');", - params![], + paramsv![], ) .await?; sql.execute( "CREATE INDEX leftgrps_index1 ON leftgrps (grpid);", - params![], + paramsv![], ) .await?; dbversion = 1; @@ -935,7 +910,7 @@ async fn open( info!(context, "[migration] v2"); sql.execute( "ALTER TABLE contacts ADD COLUMN authname TEXT DEFAULT '';", - params![], + paramsv![], ) .await?; dbversion = 2; @@ -951,7 +926,7 @@ async fn open( private_key, \ public_key, \ created INTEGER DEFAULT 0);", - params![], + paramsv![], ) .await?; dbversion = 7; @@ -967,12 +942,12 @@ async fn open( last_seen_autocrypt INTEGER DEFAULT 0, \ public_key, \ prefer_encrypted INTEGER DEFAULT 0);", - params![], + paramsv![], ) .await?; sql.execute( "CREATE INDEX acpeerstates_index1 ON acpeerstates (addr);", - params![], + paramsv![], ) .await?; dbversion = 10; @@ -982,12 +957,12 @@ async fn open( info!(context, "[migration] v12"); sql.execute( "CREATE TABLE msgs_mdns ( msg_id INTEGER, contact_id INTEGER);", - params![], + paramsv![], ) .await?; sql.execute( "CREATE INDEX msgs_mdns_index1 ON msgs_mdns (msg_id);", - params![], + paramsv![], ) .await?; dbversion = 12; @@ -997,17 +972,17 @@ async fn open( info!(context, "[migration] v17"); sql.execute( "ALTER TABLE chats ADD COLUMN archived INTEGER DEFAULT 0;", - params![], + paramsv![], ) .await?; - sql.execute("CREATE INDEX chats_index2 ON chats (archived);", params![]) + sql.execute("CREATE INDEX chats_index2 ON chats (archived);", paramsv![]) .await?; sql.execute( "ALTER TABLE msgs ADD COLUMN starred INTEGER DEFAULT 0;", - params![], + paramsv![], ) .await?; - sql.execute("CREATE INDEX msgs_index5 ON msgs (starred);", params![]) + sql.execute("CREATE INDEX msgs_index5 ON msgs (starred);", paramsv![]) .await?; dbversion = 17; sql.set_raw_config_int(context, "dbversion", 17).await?; @@ -1016,11 +991,14 @@ async fn open( info!(context, "[migration] v18"); sql.execute( "ALTER TABLE acpeerstates ADD COLUMN gossip_timestamp INTEGER DEFAULT 0;", - params![], + paramsv![], + ) + .await?; + sql.execute( + "ALTER TABLE acpeerstates ADD COLUMN gossip_key;", + paramsv![], ) .await?; - sql.execute("ALTER TABLE acpeerstates ADD COLUMN gossip_key;", params![]) - .await?; dbversion = 18; sql.set_raw_config_int(context, "dbversion", 18).await?; } @@ -1028,21 +1006,21 @@ async fn open( info!(context, "[migration] v27"); // chat.id=1 and chat.id=2 are the old deaddrops, // the current ones are defined by chats.blocked=2 - sql.execute("DELETE FROM msgs WHERE chat_id=1 OR chat_id=2;", params![]) + sql.execute("DELETE FROM msgs WHERE chat_id=1 OR chat_id=2;", paramsv![]) .await?; sql.execute( "CREATE INDEX chats_contacts_index2 ON chats_contacts (contact_id);", - params![], + paramsv![], ) .await?; sql.execute( "ALTER TABLE msgs ADD COLUMN timestamp_sent INTEGER DEFAULT 0;", - params![], + paramsv![], ) .await?; sql.execute( "ALTER TABLE msgs ADD COLUMN timestamp_rcvd INTEGER DEFAULT 0;", - params![], + paramsv![], ) .await?; dbversion = 27; @@ -1052,32 +1030,32 @@ async fn open( info!(context, "[migration] v34"); sql.execute( "ALTER TABLE msgs ADD COLUMN hidden INTEGER DEFAULT 0;", - params![], + paramsv![], ) .await?; sql.execute( "ALTER TABLE msgs_mdns ADD COLUMN timestamp_sent INTEGER DEFAULT 0;", - params![], + paramsv![], ) .await?; sql.execute( "ALTER TABLE acpeerstates ADD COLUMN public_key_fingerprint TEXT DEFAULT '';", - params![], + paramsv![], ) .await?; sql.execute( "ALTER TABLE acpeerstates ADD COLUMN gossip_key_fingerprint TEXT DEFAULT '';", - params![], + paramsv![], ) .await?; sql.execute( "CREATE INDEX acpeerstates_index3 ON acpeerstates (public_key_fingerprint);", - params![], + paramsv![], ) .await?; sql.execute( "CREATE INDEX acpeerstates_index4 ON acpeerstates (gossip_key_fingerprint);", - params![], + paramsv![], ) .await?; recalc_fingerprints = true; @@ -1088,21 +1066,21 @@ async fn open( info!(context, "[migration] v39"); sql.execute( "CREATE TABLE tokens ( id INTEGER PRIMARY KEY, namespc INTEGER DEFAULT 0, foreign_id INTEGER DEFAULT 0, token TEXT DEFAULT '', timestamp INTEGER DEFAULT 0);", - params![] + paramsv![] ).await?; sql.execute( "ALTER TABLE acpeerstates ADD COLUMN verified_key;", - params![], + paramsv![], ) .await?; sql.execute( "ALTER TABLE acpeerstates ADD COLUMN verified_key_fingerprint TEXT DEFAULT '';", - params![], + paramsv![], ) .await?; sql.execute( "CREATE INDEX acpeerstates_index5 ON acpeerstates (verified_key_fingerprint);", - params![], + paramsv![], ) .await?; dbversion = 39; @@ -1112,7 +1090,7 @@ async fn open( info!(context, "[migration] v40"); sql.execute( "ALTER TABLE jobs ADD COLUMN thread INTEGER DEFAULT 0;", - params![], + paramsv![], ) .await?; dbversion = 40; @@ -1120,7 +1098,7 @@ async fn open( } if dbversion < 44 { info!(context, "[migration] v44"); - sql.execute("ALTER TABLE msgs ADD COLUMN mime_headers TEXT;", params![]) + sql.execute("ALTER TABLE msgs ADD COLUMN mime_headers TEXT;", paramsv![]) .await?; dbversion = 44; sql.set_raw_config_int(context, "dbversion", 44).await?; @@ -1129,12 +1107,12 @@ async fn open( info!(context, "[migration] v46"); sql.execute( "ALTER TABLE msgs ADD COLUMN mime_in_reply_to TEXT;", - params![], + paramsv![], ) .await?; sql.execute( "ALTER TABLE msgs ADD COLUMN mime_references TEXT;", - params![], + paramsv![], ) .await?; dbversion = 46; @@ -1144,7 +1122,7 @@ async fn open( info!(context, "[migration] v47"); sql.execute( "ALTER TABLE jobs ADD COLUMN tries INTEGER DEFAULT 0;", - params![], + paramsv![], ) .await?; dbversion = 47; @@ -1155,7 +1133,7 @@ async fn open( // NOTE: move_state is not used anymore sql.execute( "ALTER TABLE msgs ADD COLUMN move_state INTEGER DEFAULT 1;", - params![], + paramsv![], ) .await?; @@ -1166,7 +1144,7 @@ async fn open( info!(context, "[migration] v49"); sql.execute( "ALTER TABLE chats ADD COLUMN gossiped_timestamp INTEGER DEFAULT 0;", - params![], + paramsv![], ) .await?; dbversion = 49; @@ -1190,36 +1168,36 @@ async fn open( // are also added to the database as _hidden_. sql.execute( "CREATE TABLE locations ( id INTEGER PRIMARY KEY AUTOINCREMENT, latitude REAL DEFAULT 0.0, longitude REAL DEFAULT 0.0, accuracy REAL DEFAULT 0.0, timestamp INTEGER DEFAULT 0, chat_id INTEGER DEFAULT 0, from_id INTEGER DEFAULT 0);", - params![] + paramsv![] ).await?; sql.execute( "CREATE INDEX locations_index1 ON locations (from_id);", - params![], + paramsv![], ) .await?; sql.execute( "CREATE INDEX locations_index2 ON locations (timestamp);", - params![], + paramsv![], ) .await?; sql.execute( "ALTER TABLE chats ADD COLUMN locations_send_begin INTEGER DEFAULT 0;", - params![], + paramsv![], ) .await?; sql.execute( "ALTER TABLE chats ADD COLUMN locations_send_until INTEGER DEFAULT 0;", - params![], + paramsv![], ) .await?; sql.execute( "ALTER TABLE chats ADD COLUMN locations_last_sent INTEGER DEFAULT 0;", - params![], + paramsv![], ) .await?; sql.execute( "CREATE INDEX chats_index3 ON chats (locations_send_until);", - params![], + paramsv![], ) .await?; dbversion = 53; @@ -1229,11 +1207,14 @@ async fn open( info!(context, "[migration] v54"); sql.execute( "ALTER TABLE msgs ADD COLUMN location_id INTEGER DEFAULT 0;", - params![], + paramsv![], + ) + .await?; + sql.execute( + "CREATE INDEX msgs_index6 ON msgs (location_id);", + paramsv![], ) .await?; - sql.execute("CREATE INDEX msgs_index6 ON msgs (location_id);", params![]) - .await?; dbversion = 54; sql.set_raw_config_int(context, "dbversion", 54).await?; } @@ -1241,7 +1222,7 @@ async fn open( info!(context, "[migration] v55"); sql.execute( "ALTER TABLE locations ADD COLUMN independent INTEGER DEFAULT 0;", - params![], + paramsv![], ) .await?; sql.set_raw_config_int(context, "dbversion", 55).await?; @@ -1252,11 +1233,11 @@ async fn open( // so, msg_id may or may not exist. sql.execute( "CREATE TABLE devmsglabels (id INTEGER PRIMARY KEY AUTOINCREMENT, label TEXT, msg_id INTEGER DEFAULT 0);", - NO_PARAMS, + paramsv![], ).await?; sql.execute( "CREATE INDEX devmsglabels_index1 ON devmsglabels (label);", - NO_PARAMS, + paramsv![], ) .await?; if exists_before_update && sql.get_raw_config_int(context, "bcc_self").await.is_none() { @@ -1268,7 +1249,7 @@ async fn open( info!(context, "[migration] v60"); sql.execute( "ALTER TABLE chats ADD COLUMN created_timestamp INTEGER DEFAULT 0;", - NO_PARAMS, + paramsv![], ) .await?; sql.set_raw_config_int(context, "dbversion", 60).await?; @@ -1277,7 +1258,7 @@ async fn open( info!(context, "[migration] v61"); sql.execute( "ALTER TABLE contacts ADD COLUMN selfavatar_sent INTEGER DEFAULT 0;", - NO_PARAMS, + paramsv![], ) .await?; update_icons = true; @@ -1287,14 +1268,14 @@ async fn open( info!(context, "[migration] v62"); sql.execute( "ALTER TABLE chats ADD COLUMN muted_until INTEGER DEFAULT 0;", - NO_PARAMS, + paramsv![], ) .await?; sql.set_raw_config_int(context, "dbversion", 62).await?; } if dbversion < 63 { info!(context, "[migration] v63"); - sql.execute("UPDATE chats SET grpid='' WHERE type=100", NO_PARAMS) + sql.execute("UPDATE chats SET grpid='' WHERE type=100", paramsv![]) .await?; sql.set_raw_config_int(context, "dbversion", 63).await?; } @@ -1305,22 +1286,24 @@ async fn open( if recalc_fingerprints { info!(context, "[migration] recalc fingerprints"); - sql.query_map_async( - "SELECT addr FROM acpeerstates;", - params![], - |row| row.get::<_, String>(0), - |addrs| async move { - for addr in addrs { - if let Some(ref mut peerstate) = Peerstate::from_addr(context, sql, &addr?) - { - peerstate.recalc_fingerprint(); - peerstate.save_to_db(sql, false).await?; - } - } - Ok(()) - }, - ) - .await?; + let addrs = sql + .query_map( + "SELECT addr FROM acpeerstates;", + paramsv![], + |row| row.get::<_, String>(0), + |addrs| { + addrs + .collect::, _>>() + .map_err(Into::into) + }, + ) + .await?; + for addr in &addrs { + if let Some(ref mut peerstate) = Peerstate::from_addr(context, sql, addr).await { + peerstate.recalc_fingerprint(); + peerstate.save_to_db(sql, false).await?; + } + } } if update_icons { update_saved_messages_icon(context).await?; diff --git a/src/stock.rs b/src/stock.rs index 8ce11cc33..0e30fc794 100644 --- a/src/stock.rs +++ b/src/stock.rs @@ -206,7 +206,7 @@ impl StockMessage { impl Context { /// Set the stock string for the [StockMessage]. /// - pub fn set_stock_translation( + pub async fn set_stock_translation( &self, id: StockMessage, stockstring: String, @@ -227,7 +227,7 @@ impl Context { } self.translated_stockstrings .write() - .unwrap() + .await .insert(id as usize, stockstring); Ok(()) } @@ -236,11 +236,11 @@ impl Context { /// /// Return a translation (if it was set with set_stock_translation before) /// or a default (English) string. - pub fn stock_str(&self, id: StockMessage) -> Cow { + pub async fn stock_str(&self, id: StockMessage) -> Cow<'_, str> { match self .translated_stockstrings .read() - .unwrap() + .await .get(&(id as usize)) { Some(ref x) => Cow::Owned((*x).to_string()), @@ -253,8 +253,9 @@ impl Context { /// This replaces both the *first* `%1$s`, `%1$d` and `%1$@` /// placeholders with the provided string. /// (the `%1$@` variant is used on iOS, the other are used on Android and Desktop) - pub fn stock_string_repl_str(&self, id: StockMessage, insert: impl AsRef) -> String { + pub async fn stock_string_repl_str(&self, id: StockMessage, insert: impl AsRef) -> String { self.stock_str(id) + .await .replacen("%1$s", insert.as_ref(), 1) .replacen("%1$d", insert.as_ref(), 1) .replacen("%1$@", insert.as_ref(), 1) @@ -264,8 +265,9 @@ impl Context { /// /// Like [Context::stock_string_repl_str] but substitute the placeholders /// with an integer. - pub fn stock_string_repl_int(&self, id: StockMessage, insert: i32) -> String { + pub async fn stock_string_repl_int(&self, id: StockMessage, insert: i32) -> String { self.stock_string_repl_str(id, format!("{}", insert).as_str()) + .await } /// Return stock string, replacing 2 placeholders with provided string. @@ -274,13 +276,14 @@ impl Context { /// placeholders with the string in `insert` and does the same for /// `%2$s`, `%2$d` and `%2$@` for `insert2`. /// (the `%1$@` variant is used on iOS, the other are used on Android and Desktop) - pub fn stock_string_repl_str2( + pub async fn stock_string_repl_str2( &self, id: StockMessage, insert: impl AsRef, insert2: impl AsRef, ) -> String { self.stock_str(id) + .await .replacen("%1$s", insert.as_ref(), 1) .replacen("%1$d", insert.as_ref(), 1) .replacen("%1$@", insert.as_ref(), 1) @@ -306,7 +309,7 @@ impl Context { /// used as the second parameter to [StockMessage::MsgActionByUser] with /// again the original stock string being used as the first parameter, /// resulting in a string like "Member Alice added by Bob.". - pub fn stock_system_msg( + pub async fn stock_system_msg( &self, id: StockMessage, param1: impl AsRef, @@ -314,9 +317,10 @@ impl Context { from_id: u32, ) -> String { let insert1 = if id == StockMessage::MsgAddMember || id == StockMessage::MsgDelMember { - let contact_id = Contact::lookup_id_by_addr(self, param1.as_ref()); + let contact_id = Contact::lookup_id_by_addr(self, param1.as_ref()).await; if contact_id != 0 { Contact::get_by_id(self, contact_id) + .await .map(|contact| contact.get_name_n_addr()) .unwrap_or_default() } else { @@ -326,52 +330,60 @@ impl Context { param1.as_ref().to_string() }; - let action = self.stock_string_repl_str2(id, insert1, param2.as_ref().to_string()); + let action = self + .stock_string_repl_str2(id, insert1, param2.as_ref().to_string()) + .await; let action1 = action.trim_end_matches('.'); match from_id { 0 => action, - 1 => self.stock_string_repl_str(StockMessage::MsgActionByMe, action1), // DC_CONTACT_ID_SELF + 1 => { + self.stock_string_repl_str(StockMessage::MsgActionByMe, action1) + .await + } // DC_CONTACT_ID_SELF _ => { let displayname = Contact::get_by_id(self, from_id) + .await .map(|contact| contact.get_name_n_addr()) .unwrap_or_default(); self.stock_string_repl_str2(StockMessage::MsgActionByUser, action1, &displayname) + .await } } } - pub fn update_device_chats(&self) -> Result<(), Error> { + pub async fn update_device_chats(&self) -> Result<(), Error> { // check for the LAST added device message - if it is present, we can skip message creation. // this is worthwhile as this function is typically called // by the ui on every probram start or even on every opening of the chatlist. - if chat::was_device_msg_ever_added(&self, "core-welcome")? { + if chat::was_device_msg_ever_added(&self, "core-welcome").await? { return Ok(()); } // create saved-messages chat; // we do this only once, if the user has deleted the chat, he can recreate it manually. - if !self.sql.get_raw_config_bool(&self, "self-chat-added") { + if !self.sql.get_raw_config_bool(&self, "self-chat-added").await { self.sql - .set_raw_config_bool(&self, "self-chat-added", true)?; - chat::create_by_contact_id(&self, DC_CONTACT_ID_SELF)?; + .set_raw_config_bool(&self, "self-chat-added", true) + .await?; + chat::create_by_contact_id(&self, DC_CONTACT_ID_SELF).await?; } // add welcome-messages. by the label, this is done only once, // if the user has deleted the message or the chat, it is not added again. let mut msg = Message::new(Viewtype::Text); - msg.text = Some(self.stock_str(DeviceMessagesHint).to_string()); - chat::add_device_msg(&self, Some("core-about-device-chat"), Some(&mut msg))?; + msg.text = Some(self.stock_str(DeviceMessagesHint).await.to_string()); + chat::add_device_msg(&self, Some("core-about-device-chat"), Some(&mut msg)).await?; let image = include_bytes!("../assets/welcome-image.jpg"); let blob = BlobObject::create(&self, "welcome-image.jpg".to_string(), image)?; let mut msg = Message::new(Viewtype::Image); msg.param.set(Param::File, blob.as_name()); - chat::add_device_msg(&self, Some("core-welcome-image"), Some(&mut msg))?; + chat::add_device_msg(&self, Some("core-welcome-image"), Some(&mut msg)).await?; let mut msg = Message::new(Viewtype::Text); - msg.text = Some(self.stock_str(WelcomeMessage).to_string()); - chat::add_device_msg(&self, Some("core-welcome"), Some(&mut msg))?; + msg.text = Some(self.stock_str(WelcomeMessage).await.to_string()); + chat::add_device_msg(&self, Some("core-welcome"), Some(&mut msg)).await?; Ok(()) } } @@ -397,153 +409,178 @@ mod tests { assert_eq!(StockMessage::NoMessages.fallback(), "No messages."); } - #[test] - fn test_set_stock_translation() { - let t = dummy_context(); + #[async_std::test] + async fn test_set_stock_translation() { + let t = dummy_context().await; t.ctx .set_stock_translation(StockMessage::NoMessages, "xyz".to_string()) + .await .unwrap(); - assert_eq!(t.ctx.stock_str(StockMessage::NoMessages), "xyz") + assert_eq!(t.ctx.stock_str(StockMessage::NoMessages).await, "xyz") } - #[test] - fn test_set_stock_translation_wrong_replacements() { - let t = dummy_context(); + #[async_std::test] + async fn test_set_stock_translation_wrong_replacements() { + let t = dummy_context().await; assert!(t .ctx .set_stock_translation(StockMessage::NoMessages, "xyz %1$s ".to_string()) + .await .is_err()); assert!(t .ctx .set_stock_translation(StockMessage::NoMessages, "xyz %2$s ".to_string()) + .await .is_err()); } - #[test] - fn test_stock_str() { - let t = dummy_context(); - assert_eq!(t.ctx.stock_str(StockMessage::NoMessages), "No messages."); + #[async_std::test] + async fn test_stock_str() { + let t = dummy_context().await; + assert_eq!( + t.ctx.stock_str(StockMessage::NoMessages).await, + "No messages." + ); } - #[test] - fn test_stock_string_repl_str() { - let t = dummy_context(); + #[async_std::test] + async fn test_stock_string_repl_str() { + let t = dummy_context().await; // uses %1$s substitution assert_eq!( - t.ctx.stock_string_repl_str(StockMessage::Member, "42"), + t.ctx + .stock_string_repl_str(StockMessage::Member, "42") + .await, "42 member(s)" ); // We have no string using %1$d to test... } - #[test] - fn test_stock_string_repl_int() { - let t = dummy_context(); + #[async_std::test] + async fn test_stock_string_repl_int() { + let t = dummy_context().await; assert_eq!( - t.ctx.stock_string_repl_int(StockMessage::Member, 42), + t.ctx.stock_string_repl_int(StockMessage::Member, 42).await, "42 member(s)" ); } - #[test] - fn test_stock_string_repl_str2() { - let t = dummy_context(); + #[async_std::test] + async fn test_stock_string_repl_str2() { + let t = dummy_context().await; assert_eq!( t.ctx - .stock_string_repl_str2(StockMessage::ServerResponse, "foo", "bar"), + .stock_string_repl_str2(StockMessage::ServerResponse, "foo", "bar") + .await, "Could not connect to foo: bar" ); } - #[test] - fn test_stock_system_msg_simple() { - let t = dummy_context(); + #[async_std::test] + async fn test_stock_system_msg_simple() { + let t = dummy_context().await; assert_eq!( t.ctx - .stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0), + .stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0) + .await, "Location streaming enabled." ) } - #[test] - fn test_stock_system_msg_add_member_by_me() { - let t = dummy_context(); + #[async_std::test] + async fn test_stock_system_msg_add_member_by_me() { + let t = dummy_context().await; assert_eq!( - t.ctx.stock_system_msg( - StockMessage::MsgAddMember, - "alice@example.com", - "", - DC_CONTACT_ID_SELF - ), + t.ctx + .stock_system_msg( + StockMessage::MsgAddMember, + "alice@example.com", + "", + DC_CONTACT_ID_SELF + ) + .await, "Member alice@example.com added by me." ) } - #[test] - fn test_stock_system_msg_add_member_by_me_with_displayname() { - let t = dummy_context(); - Contact::create(&t.ctx, "Alice", "alice@example.com").expect("failed to create contact"); + #[async_std::test] + async fn test_stock_system_msg_add_member_by_me_with_displayname() { + let t = dummy_context().await; + Contact::create(&t.ctx, "Alice", "alice@example.com") + .await + .expect("failed to create contact"); assert_eq!( - t.ctx.stock_system_msg( - StockMessage::MsgAddMember, - "alice@example.com", - "", - DC_CONTACT_ID_SELF - ), + t.ctx + .stock_system_msg( + StockMessage::MsgAddMember, + "alice@example.com", + "", + DC_CONTACT_ID_SELF + ) + .await, "Member Alice (alice@example.com) added by me." ); } - #[test] - fn test_stock_system_msg_add_member_by_other_with_displayname() { - let t = dummy_context(); + #[async_std::test] + async fn test_stock_system_msg_add_member_by_other_with_displayname() { + let t = dummy_context().await; let contact_id = { Contact::create(&t.ctx, "Alice", "alice@example.com") + .await .expect("Failed to create contact Alice"); - Contact::create(&t.ctx, "Bob", "bob@example.com").expect("failed to create bob") + Contact::create(&t.ctx, "Bob", "bob@example.com") + .await + .expect("failed to create bob") }; assert_eq!( - t.ctx.stock_system_msg( - StockMessage::MsgAddMember, - "alice@example.com", - "", - contact_id, - ), + t.ctx + .stock_system_msg( + StockMessage::MsgAddMember, + "alice@example.com", + "", + contact_id, + ) + .await, "Member Alice (alice@example.com) added by Bob (bob@example.com)." ); } - #[test] - fn test_stock_system_msg_grp_name() { - let t = dummy_context(); + #[async_std::test] + async fn test_stock_system_msg_grp_name() { + let t = dummy_context().await; assert_eq!( - t.ctx.stock_system_msg( - StockMessage::MsgGrpName, - "Some chat", - "Other chat", - DC_CONTACT_ID_SELF - ), + t.ctx + .stock_system_msg( + StockMessage::MsgGrpName, + "Some chat", + "Other chat", + DC_CONTACT_ID_SELF + ) + .await, "Group name changed from \"Some chat\" to \"Other chat\" by me." ) } - #[test] - fn test_stock_system_msg_grp_name_other() { - let t = dummy_context(); + #[async_std::test] + async fn test_stock_system_msg_grp_name_other() { + let t = dummy_context().await; let id = Contact::create(&t.ctx, "Alice", "alice@example.com") + .await .expect("failed to create contact"); assert_eq!( t.ctx - .stock_system_msg(StockMessage::MsgGrpName, "Some chat", "Other chat", id,), + .stock_system_msg(StockMessage::MsgGrpName, "Some chat", "Other chat", id) + .await, "Group name changed from \"Some chat\" to \"Other chat\" by Alice (alice@example.com)." ) } #[async_std::test] async fn test_update_device_chats() { - let t = dummy_context(); - t.ctx.update_device_chats().ok(); + let t = dummy_context().await; + t.ctx.update_device_chats().await.ok(); let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); assert_eq!(chats.len(), 2); @@ -553,7 +590,7 @@ mod tests { assert_eq!(chats.len(), 0); // a subsequent call to update_device_chats() must not re-add manally deleted messages or chats - t.ctx.update_device_chats().ok(); + t.ctx.update_device_chats().await.ok(); let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); assert_eq!(chats.len(), 0); } diff --git a/src/test_utils.rs b/src/test_utils.rs index 681f57fa1..f384df70f 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -25,14 +25,14 @@ pub(crate) struct TestContext { /// "db.sqlite" in the [TestContext.dir] directory. /// /// [Context]: crate::context::Context -pub(crate) fn test_context(callback: Option>) -> TestContext { +pub(crate) async fn test_context(callback: Option>) -> TestContext { let dir = tempdir().unwrap(); let dbfile = dir.path().join("db.sqlite"); let cb: Box = match callback { Some(cb) => cb, None => Box::new(|_, _| ()), }; - let ctx = Context::new(cb, "FakeOs".into(), dbfile).unwrap(); + let ctx = Context::new(cb, "FakeOs".into(), dbfile).await.unwrap(); TestContext { ctx, dir } } @@ -41,8 +41,8 @@ pub(crate) fn test_context(callback: Option>) -> TestContex /// The context will be opened and use the SQLite database as /// specified in [test_context] but there is no callback hooked up, /// i.e. [Context::call_cb] will always return `0`. -pub(crate) fn dummy_context() -> TestContext { - test_context(None) +pub(crate) async fn dummy_context() -> TestContext { + test_context(None).await } pub(crate) fn logging_cb(_ctx: &Context, evt: Event) { diff --git a/src/token.rs b/src/token.rs index ac2b4af02..b3a89d5b4 100644 --- a/src/token.rs +++ b/src/token.rs @@ -34,7 +34,7 @@ pub async fn save(context: &Context, namespace: Namespace, foreign_id: ChatId) - .sql .execute( "INSERT INTO tokens (namespc, foreign_id, token, timestamp) VALUES (?, ?, ?, ?);", - params![namespace, foreign_id, &token, time()], + paramsv![namespace, foreign_id, token, time()], ) .await .ok(); @@ -44,10 +44,10 @@ pub async fn save(context: &Context, namespace: Namespace, foreign_id: ChatId) - pub async fn lookup(context: &Context, namespace: Namespace, foreign_id: ChatId) -> Option { context .sql - .query_get_value::<_, String>( + .query_get_value::( context, "SELECT token FROM tokens WHERE namespc=? AND foreign_id=?;", - params![namespace, foreign_id], + paramsv![namespace, foreign_id], ) .await } @@ -65,7 +65,7 @@ pub async fn exists(context: &Context, namespace: Namespace, token: &str) -> boo .sql .exists( "SELECT id FROM tokens WHERE namespc=? AND token=?;", - params![namespace, token], + paramsv![namespace, token], ) .await .unwrap_or_default() diff --git a/tests/stress.rs b/tests/stress.rs index 55e9e35a8..e4ec5e873 100644 --- a/tests/stress.rs +++ b/tests/stress.rs @@ -8,8 +8,11 @@ use tempfile::{tempdir, TempDir}; /* some data used for testing ******************************************************************************/ -fn stress_functions(context: &Context) { - let res = context.get_config(config::Config::SysConfigKeys).unwrap(); +async fn stress_functions(context: &Context) { + let res = context + .get_config(config::Config::SysConfigKeys) + .await + .unwrap(); assert!(!res.contains(" probably_never_a_key ")); assert!(res.contains(" addr ")); @@ -98,15 +101,17 @@ struct TestContext { dir: TempDir, } -fn create_test_context() -> TestContext { +async fn create_test_context() -> TestContext { let dir = tempdir().unwrap(); let dbfile = dir.path().join("db.sqlite"); - let ctx = Context::new(Box::new(cb), "FakeOs".into(), dbfile).unwrap(); + let ctx = Context::new(Box::new(cb), "FakeOs".into(), dbfile) + .await + .unwrap(); TestContext { ctx, dir } } -#[test] -fn test_stress_tests() { - let context = create_test_context(); - stress_functions(&context.ctx); +#[async_std::test] +async fn test_stress_tests() { + let context = create_test_context().await; + stress_functions(&context.ctx).await; } From 4c9d049b10ce4ae75f584f5e918a2b0b771679d9 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Sun, 8 Mar 2020 18:34:00 +0100 Subject: [PATCH 008/118] blocking for with_conn --- src/chat.rs | 4 +- src/dc_receive_imf.rs | 85 ++++++++++++++++++++++--------------- src/location.rs | 97 ++++++++++++++++++++++--------------------- src/message.rs | 20 ++++----- src/sql.rs | 32 +++++++------- 5 files changed, 130 insertions(+), 108 deletions(-) diff --git a/src/chat.rs b/src/chat.rs index af4ef43d4..4b4868b7c 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -1225,11 +1225,11 @@ pub(crate) async fn create_or_lookup_by_contact_id( } let contact = Contact::load_from_db(context, contact_id).await?; - let chat_name = contact.get_display_name(); + let chat_name = contact.get_display_name().to_string(); context .sql - .with_conn(|mut conn| { + .with_conn(move |mut conn| { let conn2 = &mut conn; let tx = conn2.transaction()?; tx.execute( diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index 91c4b3f23..688ee5611 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -586,13 +586,32 @@ async fn add_parts( // (eg. one per attachment)) let icnt = mime_parser.parts.len(); - context - .sql - .with_conn(|mut conn| { - let subject = mime_parser.get_subject().unwrap_or_default(); - let mut txt_raw = None; + let subject = mime_parser.get_subject().unwrap_or_default(); + + let mut parts = std::mem::replace(&mut mime_parser.parts, Vec::new()); + let server_folder = server_folder.as_ref().to_string(); + let location_kml_is = mime_parser.location_kml.is_some(); + let is_system_message = mime_parser.is_system_message; + let mime_headers = if save_mime_headers { + Some(String::from_utf8_lossy(imf_raw).to_string()) + } else { + None + }; + let sent_timestamp = *sent_timestamp; + let is_hidden = *hidden; + let chat_id = *chat_id; + + // TODO: can this clone be avoided? + let rfc724_mid = rfc724_mid.to_string(); + + let (new_parts, ids, is_hidden) = context + .sql + .with_conn(move |mut conn| { + let mut ids = Vec::with_capacity(parts.len()); + for part in &mut parts { + let mut txt_raw = "".to_string(); + let mut is_hidden = is_hidden; - for part in mime_parser.parts.iter_mut() { let mut stmt = conn.prepare_cached( "INSERT INTO msgs \ (rfc724_mid, server_folder, server_uid, chat_id, from_id, to_id, timestamp, \ @@ -601,11 +620,9 @@ async fn add_parts( VALUES (?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?);", )?; - if mime_parser.location_kml.is_some() - && icnt == 1 - && (part.msg == "-location-" || part.msg.is_empty()) + if location_kml_is && icnt == 1 && (part.msg == "-location-" || part.msg.is_empty()) { - *hidden = true; + is_hidden = true; if state == MessageState::InFresh { state = MessageState::InNoticed; } @@ -613,57 +630,59 @@ async fn add_parts( if part.typ == Viewtype::Text { let msg_raw = part.msg_raw.as_ref().cloned().unwrap_or_default(); - txt_raw = Some(format!("{}\n\n{}", subject, msg_raw)); + txt_raw = format!("{}\n\n{}", subject, msg_raw); } - if mime_parser.is_system_message != SystemMessage::Unknown { - part.param - .set_int(Param::Cmd, mime_parser.is_system_message as i32); + if is_system_message != SystemMessage::Unknown { + part.param.set_int(Param::Cmd, is_system_message as i32); } stmt.execute(paramsv![ rfc724_mid, - server_folder.as_ref().to_string(), + server_folder, server_uid as i32, - *chat_id, + chat_id, from_id as i32, to_id as i32, sort_timestamp, - *sent_timestamp, + sent_timestamp, rcvd_timestamp, part.typ, state, msgrmsg, part.msg, // txt_raw might contain invalid utf8 - txt_raw.unwrap_or_default(), + txt_raw, part.param.to_string(), part.bytes as isize, - *hidden, - if save_mime_headers { - Some(String::from_utf8_lossy(imf_raw)) - } else { - None - }, + is_hidden, + mime_headers, mime_in_reply_to, mime_references, ])?; - txt_raw = None; - - // This is okay, as we use a cached prepared statement. drop(stmt); - let row_id = - crate::sql::get_rowid(context, &mut conn, "msgs", "rfc724_mid", &rfc724_mid)?; - *insert_msg_id = MsgId::new(row_id); - created_db_entries.push((*chat_id, *insert_msg_id)); + ids.push(MsgId::new(crate::sql::get_rowid( + &mut conn, + "msgs", + "rfc724_mid", + &rfc724_mid, + )?)); } - Ok(()) + Ok((parts, ids, is_hidden)) }) .await?; + if let Some(id) = ids.iter().last() { + *insert_msg_id = *id; + } + + *hidden = is_hidden; + created_db_entries.extend(ids.iter().map(|id| (chat_id, *id))); + mime_parser.parts = new_parts; + info!( context, - "Message has {} parts and is assigned to chat #{}.", icnt, *chat_id, + "Message has {} parts and is assigned to chat #{}.", icnt, chat_id, ); // check event to send diff --git a/src/location.rs b/src/location.rs index 728f8c654..9e3f43205 100644 --- a/src/location.rs +++ b/src/location.rs @@ -521,13 +521,15 @@ pub async fn save( ) -> Result { ensure!(!chat_id.is_special(), "Invalid chat id"); - let newest_location_id = context - .sql - .with_conn(|mut conn| { - let mut newest_timestamp = 0; - let mut newest_location_id = 0; + let mut newest_timestamp = 0; + let mut newest_location_id = 0; - for location in locations { + for location in locations { + // TODO: can this clone be avoided? + let location = location.clone(); + context + .sql + .with_conn(move |mut conn| { let mut stmt_test = conn .prepare_cached("SELECT id FROM locations WHERE timestamp=? AND from_id=?")?; let mut stmt_insert = conn.prepare_cached( @@ -555,7 +557,6 @@ pub async fn save( drop(stmt_insert); newest_timestamp = location.timestamp; newest_location_id = crate::sql::get_rowid2( - context, &mut conn, "locations", "timestamp", @@ -565,10 +566,11 @@ pub async fn save( )?; } } - } - Ok(newest_location_id) - }) - .await?; + Ok(()) + }) + .await?; + } + Ok(newest_location_id) } @@ -580,7 +582,7 @@ pub(crate) async fn job_maybe_send_locations(context: &Context, _job: &Job) -> j " ----------------- MAYBE_SEND_LOCATIONS -------------- ", ); - if let Ok(ref rows) = context + let rows = context .sql .query_map( "SELECT id, locations_send_begin, locations_last_sent \ @@ -606,11 +608,14 @@ pub(crate) async fn job_maybe_send_locations(context: &Context, _job: &Job) -> j .map_err(Into::into) }, ) - .await - { + .await; + + if rows.is_ok() { let msgs = context .sql - .with_conn(|conn| { + .with_conn(move |conn| { + let rows = rows.unwrap(); + let mut stmt_locations = conn.prepare_cached( "SELECT id \ FROM locations \ @@ -621,37 +626,34 @@ pub(crate) async fn job_maybe_send_locations(context: &Context, _job: &Job) -> j ORDER BY timestamp;", )?; - let msgs = rows - .iter() - .filter_map(|(chat_id, locations_send_begin, locations_last_sent)| { - if !stmt_locations - .exists(paramsv![ - DC_CONTACT_ID_SELF, - *locations_send_begin, - *locations_last_sent, - ]) - .unwrap_or_default() - { - // if there is no new location, there's nothing to send. - // however, maybe we want to bypass this test eg. 15 minutes - None - } else { - // pending locations are attached automatically to every message, - // so also to this empty text message. - // DC_CMD_LOCATION is only needed to create a nicer subject. - // - // for optimisation and to avoid flooding the sending queue, - // we could sending these messages only if we're really online. - // the easiest way to determine this, is to check for an empty message queue. - // (might not be 100%, however, as positions are sent combined later - // and dc_set_location() is typically called periodically, this is ok) - let mut msg = Message::new(Viewtype::Text); - msg.hidden = true; - msg.param.set_cmd(SystemMessage::LocationOnly); - Some((chat_id, msg)) - } - }) - .collect::>(); + let mut msgs = Vec::new(); + for (chat_id, locations_send_begin, locations_last_sent) in &rows { + if !stmt_locations + .exists(paramsv![ + DC_CONTACT_ID_SELF, + *locations_send_begin, + *locations_last_sent, + ]) + .unwrap_or_default() + { + // if there is no new location, there's nothing to send. + // however, maybe we want to bypass this test eg. 15 minutes + } else { + // pending locations are attached automatically to every message, + // so also to this empty text message. + // DC_CMD_LOCATION is only needed to create a nicer subject. + // + // for optimisation and to avoid flooding the sending queue, + // we could sending these messages only if we're really online. + // the easiest way to determine this, is to check for an empty message queue. + // (might not be 100%, however, as positions are sent combined later + // and dc_set_location() is typically called periodically, this is ok) + let mut msg = Message::new(Viewtype::Text); + msg.hidden = true; + msg.param.set_cmd(SystemMessage::LocationOnly); + msgs.push((*chat_id, msg)); + } + } Ok(msgs) }) @@ -660,11 +662,12 @@ pub(crate) async fn job_maybe_send_locations(context: &Context, _job: &Job) -> j for (chat_id, mut msg) in msgs.into_iter() { // TODO: better error handling - chat::send_msg(context, *chat_id, &mut msg) + chat::send_msg(context, chat_id, &mut msg) .await .unwrap_or_default(); } } + if continue_streaming { schedule_maybe_send_locations(context, true).await; } diff --git a/src/message.rs b/src/message.rs index 8ae5cd2ec..e1e874667 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1026,14 +1026,14 @@ async fn delete_poi_location(context: &Context, location_id: u32) -> bool { .is_ok() } -pub async fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool { +pub async fn markseen_msgs(context: &Context, msg_ids: Vec) -> bool { if msg_ids.is_empty() { return false; } let msgs = context .sql - .with_conn(|conn| { + .with_conn(move |conn| { let mut stmt = conn.prepare_cached(concat!( "SELECT", " m.state AS state,", @@ -1043,8 +1043,8 @@ pub async fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool { ))?; let mut msgs = Vec::with_capacity(msg_ids.len()); - for id in msg_ids.iter() { - let query_res = stmt.query_row(paramsv![*id], |row| { + for id in msg_ids.into_iter() { + let query_res = stmt.query_row(paramsv![id], |row| { Ok(( row.get::<_, MessageState>("state")?, row.get::<_, Option>("blocked")? @@ -1070,7 +1070,7 @@ pub async fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool { for (id, curr_state, curr_blocked) in msgs.into_iter() { if curr_blocked == Blocked::Not { if curr_state == MessageState::InFresh || curr_state == MessageState::InNoticed { - update_msg_state(context, *id, MessageState::InSeen).await; + update_msg_state(context, id, MessageState::InSeen).await; info!(context, "Seen message {}.", id); job::add( @@ -1084,7 +1084,7 @@ pub async fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool { send_event = true; } } else if curr_state == MessageState::InFresh { - update_msg_state(context, *id, MessageState::InNoticed).await; + update_msg_state(context, id, MessageState::InNoticed).await; send_event = true; } } @@ -1110,16 +1110,16 @@ pub async fn update_msg_state(context: &Context, msg_id: MsgId, state: MessageSt .is_ok() } -pub async fn star_msgs(context: &Context, msg_ids: &[MsgId], star: bool) -> bool { +pub async fn star_msgs(context: &Context, msg_ids: Vec, star: bool) -> bool { if msg_ids.is_empty() { return false; } context .sql - .with_conn(|conn| { + .with_conn(move |conn| { let mut stmt = conn.prepare("UPDATE msgs SET starred=? WHERE id=?;")?; - for msg_id in msg_ids.iter() { - stmt.execute(paramsv![star as i32, *msg_id])?; + for msg_id in msg_ids.into_iter() { + stmt.execute(paramsv![star as i32, msg_id])?; } Ok(()) }) diff --git a/src/sql.rs b/src/sql.rs index 30abf1da4..b32d6dfbb 100644 --- a/src/sql.rs +++ b/src/sql.rs @@ -162,19 +162,20 @@ impl Sql { Ok(conn) } - pub async fn with_conn(&self, mut g: G) -> Result + pub async fn with_conn(&self, g: G) -> Result where - G: FnMut(r2d2::PooledConnection) -> Result, + H: Send + 'static, + G: Send + + 'static + + FnOnce(r2d2::PooledConnection) -> Result, { let lock = self.pool.read().await; let pool = lock.as_ref().ok_or_else(|| Error::SqlNoConnection)?; + let conn = pool.get()?; + + let res = async_std::task::spawn_blocking(move || g(conn)).await; + self.in_use.remove(); - let res = { - let conn = pool.get()?; - let res = g(conn); - self.in_use.remove(); - res - }; res } @@ -234,9 +235,10 @@ impl Sql { pub async fn table_exists(&self, name: impl AsRef) -> Result { self.start_stmt("table_exists"); - self.with_conn(|conn| { + let name = name.as_ref().to_string(); + self.with_conn(move |conn| { let mut exists = false; - conn.pragma(None, "table_info", &name.as_ref().to_string(), |_row| { + conn.pragma(None, "table_info", &name, |_row| { // will only be executed if the info was found exists = true; Ok(()) @@ -422,7 +424,7 @@ impl Sql { /// eg. if a Message-ID is split into different messages. pub async fn get_rowid( &self, - context: &Context, + _context: &Context, table: impl AsRef, field: impl AsRef, value: impl AsRef, @@ -431,7 +433,7 @@ impl Sql { let res = { let mut conn = self.get_conn().await?; - let res = get_rowid(context, &mut conn, table, field, value); + let res = get_rowid(&mut conn, table, field, value); self.in_use.remove(); res }; @@ -441,7 +443,7 @@ impl Sql { pub async fn get_rowid2( &self, - context: &Context, + _context: &Context, table: impl AsRef, field: impl AsRef, value: i64, @@ -452,7 +454,7 @@ impl Sql { let res = { let mut conn = self.get_conn().await?; - let res = get_rowid2(context, &mut conn, table, field, value, field2, value2); + let res = get_rowid2(&mut conn, table, field, value, field2, value2); self.in_use.remove(); res }; @@ -462,7 +464,6 @@ impl Sql { } pub fn get_rowid( - _context: &Context, conn: &mut Connection, table: impl AsRef, field: impl AsRef, @@ -481,7 +482,6 @@ pub fn get_rowid( } pub fn get_rowid2( - _context: &Context, conn: &mut Connection, table: impl AsRef, field: impl AsRef, From 6db253e1cca4b8ad393d015ed4c0a63145fde449 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Sat, 14 Mar 2020 15:21:19 +0100 Subject: [PATCH 009/118] rebase fixes --- Cargo.lock | 225 +++++++++++++++++++++++------------------------- src/chatlist.rs | 17 ++-- src/imap/mod.rs | 14 +-- 3 files changed, 128 insertions(+), 128 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 197cc6aa7..9e969ed74 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -36,7 +36,7 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -78,7 +78,7 @@ name = "async-attributes" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -86,11 +86,11 @@ dependencies = [ name = "async-imap" version = "0.2.0" dependencies = [ - "async-native-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "async-native-tls 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "byte-pool 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "imap-proto 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -104,12 +104,13 @@ dependencies = [ [[package]] name = "async-native-tls" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "thiserror 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", + "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -117,7 +118,7 @@ name = "async-smtp" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "async-native-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "async-native-tls 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "async-trait 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)", "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -175,7 +176,7 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -201,10 +202,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "backtrace" -version = "0.3.44" +version = "0.3.45" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace-sys 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", @@ -212,7 +213,7 @@ dependencies = [ [[package]] name = "backtrace-sys" -version = "0.1.32" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", @@ -372,14 +373,6 @@ name = "bytes" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "c2-chacha" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "cargo_metadata" version = "0.6.4" @@ -432,7 +425,7 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", @@ -473,16 +466,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "core-foundation" -version = "0.6.4" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "core-foundation-sys" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -555,7 +548,7 @@ name = "ctor" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -588,7 +581,7 @@ dependencies = [ "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "ident_case 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "strsim 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -599,7 +592,7 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "darling_core 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -626,22 +619,22 @@ name = "deltachat" version = "1.27.0" dependencies = [ "async-imap 0.2.0", - "async-native-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "async-native-tls 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "async-smtp 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "backtrace 0.3.44 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace 0.3.45 (registry+https://github.com/rust-lang/crates.io-index)", "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "charset 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", "debug_stub_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "deltachat_derive 2.0.0", "email 0.0.21 (git+https://github.com/deltachat/rust-email)", "encoded-words 0.1.0 (git+https://github.com/async-email/encoded-words)", "escaper 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "failure_derive 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "hex 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "image 0.22.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -652,7 +645,7 @@ dependencies = [ "lettre_email 0.9.2 (git+https://github.com/deltachat/lettre)", "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", "mailparse 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)", - "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "num-derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -664,7 +657,7 @@ dependencies = [ "r2d2 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)", "r2d2_sqlite 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "reqwest 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)", "rusqlite 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", "rustyline 4.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -684,7 +677,7 @@ dependencies = [ name = "deltachat_derive" version = "2.0.0" dependencies = [ - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -693,7 +686,7 @@ name = "deltachat_ffi" version = "1.27.0" dependencies = [ "deltachat 1.27.0", - "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "human-panic 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", @@ -708,7 +701,7 @@ dependencies = [ "darling 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", "derive_builder_core 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -719,7 +712,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "darling 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -758,7 +751,7 @@ dependencies = [ [[package]] name = "doc-comment" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -788,7 +781,7 @@ version = "0.0.21" source = "git+https://github.com/deltachat/rust-email#ace12ee6f8e054dd890589f588d0311604fc25f0" dependencies = [ "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", "encoded-words 0.1.0 (git+https://github.com/async-email/encoded-words)", "encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -807,7 +800,7 @@ dependencies = [ "encoding_rs 0.8.22 (registry+https://github.com/rust-lang/crates.io-index)", "hex 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "thiserror 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -889,7 +882,7 @@ dependencies = [ "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", "humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -898,7 +891,7 @@ name = "error-chain" version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "backtrace 0.3.44 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace 0.3.45 (registry+https://github.com/rust-lang/crates.io-index)", "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -912,20 +905,20 @@ dependencies = [ [[package]] name = "failure" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "backtrace 0.3.44 (registry+https://github.com/rust-lang/crates.io-index)", - "failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace 0.3.45 (registry+https://github.com/rust-lang/crates.io-index)", + "failure_derive 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "failure_derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", "synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1051,7 +1044,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1197,8 +1190,8 @@ name = "human-panic" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "backtrace 0.3.44 (registry+https://github.com/rust-lang/crates.io-index)", - "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace 0.3.45 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "os_type 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1246,7 +1239,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.13.3 (registry+https://github.com/rust-lang/crates.io-index)", - "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "tokio 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-tls 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1286,7 +1279,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "skeptic 0.13.4 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1400,7 +1393,7 @@ dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "lettre 0.9.2 (git+https://github.com/deltachat/lettre)", "mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1413,7 +1406,7 @@ dependencies = [ "arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1577,7 +1570,7 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1587,8 +1580,8 @@ dependencies = [ "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "openssl-sys 0.9.54 (registry+https://github.com/rust-lang/crates.io-index)", "schannel 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "security-framework 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "security-framework-sys 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1662,7 +1655,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1766,7 +1759,7 @@ name = "os_type" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "regex 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.3.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1827,7 +1820,7 @@ dependencies = [ "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "cast5 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "cfb-mode 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", "circular 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "crc24 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1873,7 +1866,7 @@ version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1924,7 +1917,7 @@ name = "pretty_env_logger" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1935,7 +1928,7 @@ version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1966,7 +1959,7 @@ dependencies = [ "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)", "rusty-fork 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1999,7 +1992,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "quote" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2067,7 +2060,7 @@ dependencies = [ "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", "packed_simd 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2083,10 +2076,10 @@ dependencies = [ [[package]] name = "rand_chacha" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2200,18 +2193,18 @@ dependencies = [ [[package]] name = "regex" -version = "1.3.4" +version = "1.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "aho-corasick 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)", + "aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)", "thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "regex-syntax" -version = "0.6.16" +version = "0.6.17" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -2237,7 +2230,7 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2260,7 +2253,7 @@ dependencies = [ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", "mime_guess 2.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2292,7 +2285,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "num-bigint-dig 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2370,7 +2363,7 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -2392,7 +2385,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.3.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2419,21 +2412,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "security-framework" -version = "0.3.4" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", - "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "security-framework-sys 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "security-framework-sys" -version = "0.3.3" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2464,7 +2458,7 @@ version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2474,7 +2468,7 @@ version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2553,7 +2547,7 @@ name = "snafu" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "doc-comment 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "doc-comment 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "snafu-derive 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2563,7 +2557,7 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2616,7 +2610,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2641,7 +2635,7 @@ version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2659,7 +2653,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2716,7 +2710,7 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2767,7 +2761,7 @@ name = "tokio-tls" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "tokio 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2986,7 +2980,7 @@ dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-bindgen-shared 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -3007,7 +3001,7 @@ name = "wasm-bindgen-macro" version = "0.2.59" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-bindgen-macro-support 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -3017,7 +3011,7 @@ version = "0.2.59" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-bindgen-backend 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-bindgen-shared 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3131,7 +3125,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", "synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -3141,14 +3135,14 @@ dependencies = [ "checksum aes 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "54eb1d8fe354e5fc611daf4f2ea97dd45a765f4f1e4512306ec183ae2e8f20c9" "checksum aes-soft 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cfd7e7ae3f9a1fb5c03b389fc6bb9a51400d0c13053f0dca698c832bfd893a0d" "checksum aesni 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2f70a6b5f971e473091ab7cfb5ffac6cde81666c4556751d8d5620ead8abf100" -"checksum aho-corasick 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)" = "d5e63fd144e18ba274ae7095c0197a870a7b9468abc801dd62f190d80817d2ec" +"checksum aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)" = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" "checksum arrayref 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" "checksum arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" "checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" "checksum ascii_utils 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a" "checksum async-attributes 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "efd3d156917d94862e779f356c5acae312b08fd3121e792c857d7928c8088423" -"checksum async-native-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d40a615e861c981117e15c28c577daf9918cabd2e2d588a5e06811ae79c9da1a" +"checksum async-native-tls 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9e9e7a929bd34c68a82d58a4de7f86fffdaf97fb2af850162a7bb19dd7269b33" "checksum async-smtp 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3652e5c6072c0694a2bcdb7e8409980d2676bd4f024adf4aab10c68fd2b48f5" "checksum async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "538ecb01eb64eecd772087e5b6f7540cbc917f047727339a472dafed2185b267" "checksum async-task 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0ac2c016b079e771204030951c366db398864f5026f84a44dafb0ff20f02085d" @@ -3156,8 +3150,8 @@ dependencies = [ "checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" "checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" "checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" -"checksum backtrace 0.3.44 (registry+https://github.com/rust-lang/crates.io-index)" = "e4036b9bf40f3cf16aba72a3d65e8a520fc4bafcdc7079aea8f848c58c5b5536" -"checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491" +"checksum backtrace 0.3.45 (registry+https://github.com/rust-lang/crates.io-index)" = "ad235dabf00f36301792cfe82499880ba54c6486be094d1047b02bacb67c14e8" +"checksum backtrace-sys 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)" = "ca797db0057bae1a7aa2eef3283a874695455cecf08a43bfb8507ee0ebc1ed69" "checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" "checksum base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" "checksum bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e84c238982c4b1e1ee668d136c510c67a13465279c0cb367ea6baf6310620a80" @@ -3179,21 +3173,20 @@ dependencies = [ "checksum bytecount 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b92204551573580e078dc80017f36a213eb77a0450e4ddd8cfa0f3f2d1f0178f" "checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" "checksum bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" -"checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" "checksum cargo_metadata 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e5d1b4d380e1bab994591a24c2bdd1b054f64b60bef483a8c598c7c345bc3bbe" "checksum cast5 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ce5759b4c52ca74f9a98421817c882f1fd9b0071ae41cd61ab9f9d059c04fd6" "checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" "checksum cfb-mode 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "190e7b55d3a27cf8879becf61035a141cbc783f3258a41d16d1706719f991345" "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" "checksum charset 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4f426e64df1c3de26cbf44593c6ffff5dbfd43bbf9de0d075058558126b3fc73" -"checksum chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01" +"checksum chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" "checksum circular 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b0fc239e0f6cb375d2402d48afb92f76f5404fd1df208a41930ec81eda078bea" "checksum clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "97276801e127ffb46b66ce23f35cc96bd454fa311294bced4bbace7baa8b1d17" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum color_quant 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0dbbb57365263e881e805dc77d94697c9118fd94d8da011240555aa7b23445bd" "checksum constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" -"checksum core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" -"checksum core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" +"checksum core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" +"checksum core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" "checksum crc24 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "fd121741cf3eb82c08dd3023eb55bf2665e5f60ec20f89760cf836ae4562e6a0" "checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" "checksum crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061" @@ -3214,7 +3207,7 @@ dependencies = [ "checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" "checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" "checksum dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" -"checksum doc-comment 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "923dea538cea0aa3025e8685b20d6ee21ef99c4f77e954a30febbaac5ec73a97" +"checksum doc-comment 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "807e5847c39ad6a11eac66de492ed1406f76a260eb8656e8740cad9eabc69c27" "checksum dtoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3" "checksum ed25519-dalek 1.0.0-pre.3 (registry+https://github.com/rust-lang/crates.io-index)" = "978710b352437433c97b2bff193f2fb1dfd58a093f863dd95e225a19baa599a2" "checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" @@ -3232,8 +3225,8 @@ dependencies = [ "checksum env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3" "checksum error-chain 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d371106cc88ffdfb1eabd7111e432da544f16f3e2d7bf1dfe8bf575f1df045cd" "checksum escaper 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "39da344028c2227132b2dfa7c186e2104ecc153467583d00ed9c398f9ff693b0" -"checksum failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9" -"checksum failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08" +"checksum failure 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "b8529c2421efa3066a5cbd8063d2244603824daccb6936b079010bb2aa89464b" +"checksum failure_derive 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "030a733c8287d6213886dd487564ff5c8f6aae10278b3588ed177f9d18f8d231" "checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" "checksum fallible-iterator 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" "checksum fallible-streaming-iterator 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" @@ -3310,7 +3303,7 @@ dependencies = [ "checksum mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)" = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f" "checksum mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125" "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" -"checksum native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b2df1a4c22fd44a62147fd8f13dd0f95c9d8ca7b2610299b2a2f9cf8964274e" +"checksum native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "2b0d88c06fe90d5ee94048ba40409ef1d9315d86f6f38c2efdaad4fb50c58b2d" "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" "checksum nix 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4dbdc256eaac2e3bd236d93ad999d3479ef775c863dbda3068c4006a92eec51b" "checksum nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" @@ -3353,7 +3346,7 @@ dependencies = [ "checksum quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" "checksum quick-xml 0.17.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fe1e430bdcf30c9fdc25053b9c459bb1a4672af4617b6c783d7d91dc17c6bbb0" "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" -"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" +"checksum quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" "checksum quoted_printable 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "47b080c5db639b292ac79cbd34be0cfc5d36694768d8341109634d90b86930e2" "checksum r2d2 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1497e40855348e4a8a40767d8e55174bce1e445a3ac9254ad44ad468ee0485af" "checksum r2d2_sqlite 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b061f5b16692bbe81eeb260f92e6fc7d13aea455c4cbe67f5c4aa20aa92d1d9e" @@ -3361,7 +3354,7 @@ dependencies = [ "checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" "checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" "checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" -"checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" +"checksum rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" "checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" "checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" "checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" @@ -3375,8 +3368,8 @@ dependencies = [ "checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" "checksum redox_users 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431" -"checksum regex 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "322cf97724bea3ee221b78fe25ac9c46114ebb51747ad5babd51a2fc6a8235a8" -"checksum regex-syntax 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)" = "1132f845907680735a84409c3bebc64d1364a5683ffbce899550cd09d5eaefc1" +"checksum regex 1.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8900ebc1363efa7ea1c399ccc32daed870b4002651e0bed86e72d501ebbe0048" +"checksum regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)" = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" "checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" "checksum rental 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8545debe98b2b139fb04cad8618b530e9b07c152d99a5de83c860b877d67847f" "checksum rental-impl 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "475e68978dc5b743f2f40d8e0a8fdc83f1c5e78cbf4b8fa5e74e73beebc340de" @@ -3389,15 +3382,15 @@ dependencies = [ "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" "checksum rusty-fork 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3dd93264e10c577503e926bd1430193eeb5d21b059148910082245309b424fae" "checksum rustyline 4.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0f47ea1ceb347d2deae482d655dc8eef4bd82363d3329baffa3818bd76fea48b" -"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" +"checksum ryu 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535622e6be132bccd223f4bb2b8ac8d53cda3c7a6394944d3b2b33fb974f9d76" "checksum safemem 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" "checksum same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" "checksum sanitize-filename 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "23fd0fec94ec480abfd86bb8f4f6c57e0efb36dac5c852add176ea7b04c74801" "checksum schannel 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "507a9e6e8ffe0a4e0ebb9a10293e62fdf7657c06f1b8bb07a8fcf697d2abf295" "checksum scheduled-thread-pool 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f5de7bc31f28f8e6c28df5e1bf3d10610f5fdc14cc95f272853512c70a2bd779" "checksum scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" -"checksum security-framework 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8ef2429d7cefe5fd28bd1d2ed41c944547d4ff84776f5935b456da44593a16df" -"checksum security-framework-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e31493fc37615debb8c5090a7aeb4a9730bc61e77ab10b9af59f1a202284f895" +"checksum security-framework 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "97bbedbe81904398b6ebb054b3e912f99d55807125790f3198ac990d98def5b0" +"checksum security-framework-sys 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "06fd2f23e31ef68dd2328cc383bd493142e46107a3a0e24f7d734e3f3b80fe4c" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" "checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" diff --git a/src/chatlist.rs b/src/chatlist.rs index 3fe730181..1d56b2056 100644 --- a/src/chatlist.rs +++ b/src/chatlist.rs @@ -201,6 +201,7 @@ impl Chatlist { // show normal chatlist let sort_id_up = if 0 != listflags & DC_GCL_FOR_FORWARDING { chat::lookup_by_contact_id(context, DC_CONTACT_ID_SELF) + .await .unwrap_or_default() .0 } else { @@ -441,18 +442,22 @@ mod tests { assert_eq!(chats.len(), 1); } - #[test] - fn test_sort_self_talk_up_on_forward() { - let t = dummy_context(); - t.ctx.update_device_chats().unwrap(); + #[async_std::test] + async fn test_sort_self_talk_up_on_forward() { + let t = dummy_context().await; + t.ctx.update_device_chats().await.unwrap(); - let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap(); + let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); assert!(Chat::load_from_db(&t.ctx, chats.get_chat_id(0)) + .await .unwrap() .is_device_talk()); - let chats = Chatlist::try_load(&t.ctx, DC_GCL_FOR_FORWARDING, None, None).unwrap(); + let chats = Chatlist::try_load(&t.ctx, DC_GCL_FOR_FORWARDING, None, None) + .await + .unwrap(); assert!(Chat::load_from_db(&t.ctx, chats.get_chat_id(0)) + .await .unwrap() .is_self_talk()); } diff --git a/src/imap/mod.rs b/src/imap/mod.rs index cc802318a..ee8154fce 100644 --- a/src/imap/mod.rs +++ b/src/imap/mod.rs @@ -1270,12 +1270,14 @@ impl Imap { error!(context, "expunge failed {}: {:?}", folder, err); } } - if let Err(err) = crate::sql::execute( - context, - &context.sql, - "UPDATE msgs SET server_folder='',server_uid=0 WHERE server_folder=?", - params![folder], - ) { + if let Err(err) = context + .sql + .execute( + "UPDATE msgs SET server_folder='',server_uid=0 WHERE server_folder=?", + paramsv![folder], + ) + .await + { warn!( context, "Failed to reset server_uid and server_folder for deleted messages: {}", err From 7140898db99e153adffed899dac9bbefe8afa30d Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Sat, 14 Mar 2020 16:26:15 +0100 Subject: [PATCH 010/118] async file io --- examples/simple.rs | 2 +- src/blob.rs | 137 +++++++++++++++++++++++--------------- src/chat.rs | 32 +++++---- src/config.rs | 6 +- src/contact.rs | 3 +- src/context.rs | 32 +++++---- src/dc_tools.rs | 160 +++++++++++++++++++++++++-------------------- src/events.rs | 2 +- src/imex.rs | 108 ++++++++++++++++-------------- src/job.rs | 6 +- src/key.rs | 6 +- src/message.rs | 23 ++++--- src/mimefactory.rs | 15 +++-- src/mimeparser.rs | 11 ++-- src/param.rs | 37 +++++++---- src/sql.rs | 4 +- src/stock.rs | 2 +- src/test_utils.rs | 4 +- tests/stress.rs | 2 +- 19 files changed, 340 insertions(+), 252 deletions(-) diff --git a/examples/simple.rs b/examples/simple.rs index 2b2c6e2a4..c4a5aa827 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -36,7 +36,7 @@ async fn main() { let dir = tempdir().unwrap(); let dbfile = dir.path().join("db.sqlite"); println!("creating database {:?}", dbfile); - let ctx = Context::new(Box::new(cb), "FakeOs".into(), dbfile) + let ctx = Context::new(Box::new(cb), "FakeOs".into(), dbfile.into()) .await .expect("Failed to create context"); let running = Arc::new(RwLock::new(true)); diff --git a/src/blob.rs b/src/blob.rs index c43cff569..438fea6dd 100644 --- a/src/blob.rs +++ b/src/blob.rs @@ -2,9 +2,10 @@ use std::ffi::OsStr; use std::fmt; -use std::fs; -use std::io::Write; -use std::path::{Path, PathBuf}; + +use async_std::path::{Path, PathBuf}; +use async_std::prelude::*; +use async_std::{fs, io}; use self::image::GenericImageView; use crate::constants::AVATAR_SIZE; @@ -43,15 +44,16 @@ impl<'a> BlobObject<'a> { /// [BlobError::WriteFailure] is used when the file could not /// be written to. You can expect [BlobError.cause] to contain an /// underlying error. - pub fn create( + pub async fn create( context: &'a Context, suggested_name: impl AsRef, data: &[u8], ) -> std::result::Result, BlobError> { let blobdir = context.get_blobdir(); let (stem, ext) = BlobObject::sanitise_name(suggested_name.as_ref()); - let (name, mut file) = BlobObject::create_new_file(&blobdir, &stem, &ext)?; + let (name, mut file) = BlobObject::create_new_file(&blobdir, &stem, &ext).await?; file.write_all(data) + .await .map_err(|err| BlobError::WriteFailure { blobdir: blobdir.to_path_buf(), blobname: name.clone(), @@ -67,7 +69,11 @@ impl<'a> BlobObject<'a> { } // Creates a new file, returning a tuple of the name and the handle. - fn create_new_file(dir: &Path, stem: &str, ext: &str) -> Result<(String, fs::File), BlobError> { + async fn create_new_file( + dir: &Path, + stem: &str, + ext: &str, + ) -> Result<(String, fs::File), BlobError> { let max_attempt = 15; let mut name = format!("{}{}", stem, ext); for attempt in 0..max_attempt { @@ -76,6 +82,7 @@ impl<'a> BlobObject<'a> { .create_new(true) .write(true) .open(&path) + .await { Ok(file) => return Ok((name, file)), Err(err) => { @@ -113,34 +120,38 @@ impl<'a> BlobObject<'a> { /// In addition to the errors in [BlobObject::create] the /// [BlobError::CopyFailure] is used when the data can not be /// copied. - pub fn create_and_copy( + pub async fn create_and_copy( context: &'a Context, src: impl AsRef, ) -> std::result::Result, BlobError> { - let mut src_file = fs::File::open(src.as_ref()).map_err(|err| BlobError::CopyFailure { - blobdir: context.get_blobdir().to_path_buf(), - blobname: String::from(""), - src: src.as_ref().to_path_buf(), - cause: err, - backtrace: failure::Backtrace::new(), - })?; + let mut src_file = + fs::File::open(src.as_ref()) + .await + .map_err(|err| BlobError::CopyFailure { + blobdir: context.get_blobdir().to_path_buf(), + blobname: String::from(""), + src: src.as_ref().to_path_buf(), + cause: err, + backtrace: failure::Backtrace::new(), + })?; let (stem, ext) = BlobObject::sanitise_name(&src.as_ref().to_string_lossy()); - let (name, mut dst_file) = BlobObject::create_new_file(context.get_blobdir(), &stem, &ext)?; + let (name, mut dst_file) = + BlobObject::create_new_file(context.get_blobdir(), &stem, &ext).await?; let name_for_err = name.clone(); - std::io::copy(&mut src_file, &mut dst_file).map_err(|err| { + if let Err(err) = io::copy(&mut src_file, &mut dst_file).await { { // Attempt to remove the failed file, swallow errors resulting from that. let path = context.get_blobdir().join(&name_for_err); - fs::remove_file(path).ok(); + fs::remove_file(path).await.ok(); } - BlobError::CopyFailure { + return Err(BlobError::CopyFailure { blobdir: context.get_blobdir().to_path_buf(), blobname: name_for_err, src: src.as_ref().to_path_buf(), cause: err, backtrace: failure::Backtrace::new(), - } - })?; + }); + } let blob = BlobObject { blobdir: context.get_blobdir(), name: format!("$BLOBDIR/{}", name), @@ -163,14 +174,14 @@ impl<'a> BlobObject<'a> { /// This merely delegates to the [BlobObject::create_and_copy] and /// the [BlobObject::from_path] methods. See those for possible /// errors. - pub fn new_from_path( + pub async fn new_from_path( context: &Context, src: impl AsRef, - ) -> std::result::Result { + ) -> std::result::Result, BlobError> { if src.as_ref().starts_with(context.get_blobdir()) { BlobObject::from_path(context, src) } else { - BlobObject::create_and_copy(context, src) + BlobObject::create_and_copy(context, src).await } } @@ -489,9 +500,9 @@ mod tests { #[async_std::test] async fn test_create() { let t = dummy_context().await; - let blob = BlobObject::create(&t.ctx, "foo", b"hello").unwrap(); + let blob = BlobObject::create(&t.ctx, "foo", b"hello").await.unwrap(); let fname = t.ctx.get_blobdir().join("foo"); - let data = fs::read(fname).unwrap(); + let data = fs::read(fname).await.unwrap(); assert_eq!(data, b"hello"); assert_eq!(blob.as_name(), "$BLOBDIR/foo"); assert_eq!(blob.to_abs_path(), t.ctx.get_blobdir().join("foo")); @@ -500,44 +511,57 @@ mod tests { #[async_std::test] async fn test_lowercase_ext() { let t = dummy_context().await; - let blob = BlobObject::create(&t.ctx, "foo.TXT", b"hello").unwrap(); + let blob = BlobObject::create(&t.ctx, "foo.TXT", b"hello") + .await + .unwrap(); assert_eq!(blob.as_name(), "$BLOBDIR/foo.txt"); } #[async_std::test] async fn test_as_file_name() { let t = dummy_context().await; - let blob = BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap(); + let blob = BlobObject::create(&t.ctx, "foo.txt", b"hello") + .await + .unwrap(); assert_eq!(blob.as_file_name(), "foo.txt"); } #[async_std::test] async fn test_as_rel_path() { let t = dummy_context().await; - let blob = BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap(); + let blob = BlobObject::create(&t.ctx, "foo.txt", b"hello") + .await + .unwrap(); assert_eq!(blob.as_rel_path(), Path::new("foo.txt")); } #[async_std::test] async fn test_suffix() { let t = dummy_context().await; - let blob = BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap(); + let blob = BlobObject::create(&t.ctx, "foo.txt", b"hello") + .await + .unwrap(); assert_eq!(blob.suffix(), Some("txt")); - let blob = BlobObject::create(&t.ctx, "bar", b"world").unwrap(); + let blob = BlobObject::create(&t.ctx, "bar", b"world").await.unwrap(); assert_eq!(blob.suffix(), None); } #[async_std::test] async fn test_create_dup() { let t = dummy_context().await; - BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap(); + BlobObject::create(&t.ctx, "foo.txt", b"hello") + .await + .unwrap(); let foo_path = t.ctx.get_blobdir().join("foo.txt"); - assert!(foo_path.exists()); - BlobObject::create(&t.ctx, "foo.txt", b"world").unwrap(); - for dirent in fs::read_dir(t.ctx.get_blobdir()).unwrap() { + assert!(foo_path.exists().await); + BlobObject::create(&t.ctx, "foo.txt", b"world") + .await + .unwrap(); + let mut dir = fs::read_dir(t.ctx.get_blobdir()).await.unwrap(); + while let Some(dirent) = dir.next().await { let fname = dirent.unwrap().file_name(); if fname == foo_path.file_name().unwrap() { - assert_eq!(fs::read(&foo_path).unwrap(), b"hello"); + assert_eq!(fs::read(&foo_path).await.unwrap(), b"hello"); } else { let name = fname.to_str().unwrap(); assert!(name.starts_with("foo")); @@ -549,14 +573,19 @@ mod tests { #[async_std::test] async fn test_double_ext_preserved() { let t = dummy_context().await; - BlobObject::create(&t.ctx, "foo.tar.gz", b"hello").unwrap(); + BlobObject::create(&t.ctx, "foo.tar.gz", b"hello") + .await + .unwrap(); let foo_path = t.ctx.get_blobdir().join("foo.tar.gz"); - assert!(foo_path.exists()); - BlobObject::create(&t.ctx, "foo.tar.gz", b"world").unwrap(); - for dirent in fs::read_dir(t.ctx.get_blobdir()).unwrap() { + assert!(foo_path.exists().await); + BlobObject::create(&t.ctx, "foo.tar.gz", b"world") + .await + .unwrap(); + let mut dir = fs::read_dir(t.ctx.get_blobdir()).await.unwrap(); + while let Some(dirent) = dir.next().await { let fname = dirent.unwrap().file_name(); if fname == foo_path.file_name().unwrap() { - assert_eq!(fs::read(&foo_path).unwrap(), b"hello"); + assert_eq!(fs::read(&foo_path).await.unwrap(), b"hello"); } else { let name = fname.to_str().unwrap(); println!("{}", name); @@ -570,7 +599,7 @@ mod tests { async fn test_create_long_names() { let t = dummy_context().await; let s = "1".repeat(150); - let blob = BlobObject::create(&t.ctx, &s, b"data").unwrap(); + let blob = BlobObject::create(&t.ctx, &s, b"data").await.unwrap(); let blobname = blob.as_name().split('/').last().unwrap(); assert!(blobname.len() < 128); } @@ -579,16 +608,16 @@ mod tests { async fn test_create_and_copy() { let t = dummy_context().await; let src = t.dir.path().join("src"); - fs::write(&src, b"boo").unwrap(); - let blob = BlobObject::create_and_copy(&t.ctx, &src).unwrap(); + fs::write(&src, b"boo").await.unwrap(); + let blob = BlobObject::create_and_copy(&t.ctx, &src).await.unwrap(); assert_eq!(blob.as_name(), "$BLOBDIR/src"); - let data = fs::read(blob.to_abs_path()).unwrap(); + let data = fs::read(blob.to_abs_path()).await.unwrap(); assert_eq!(data, b"boo"); let whoops = t.dir.path().join("whoops"); - assert!(BlobObject::create_and_copy(&t.ctx, &whoops).is_err()); + assert!(BlobObject::create_and_copy(&t.ctx, &whoops).await.is_err()); let whoops = t.ctx.get_blobdir().join("whoops"); - assert!(!whoops.exists()); + assert!(!whoops.exists().await); } #[async_std::test] @@ -596,25 +625,25 @@ mod tests { let t = dummy_context().await; let src_ext = t.dir.path().join("external"); - fs::write(&src_ext, b"boo").unwrap(); - let blob = BlobObject::new_from_path(&t.ctx, &src_ext).unwrap(); + fs::write(&src_ext, b"boo").await.unwrap(); + let blob = BlobObject::new_from_path(&t.ctx, &src_ext).await.unwrap(); assert_eq!(blob.as_name(), "$BLOBDIR/external"); - let data = fs::read(blob.to_abs_path()).unwrap(); + let data = fs::read(blob.to_abs_path()).await.unwrap(); assert_eq!(data, b"boo"); let src_int = t.ctx.get_blobdir().join("internal"); - fs::write(&src_int, b"boo").unwrap(); - let blob = BlobObject::new_from_path(&t.ctx, &src_int).unwrap(); + fs::write(&src_int, b"boo").await.unwrap(); + let blob = BlobObject::new_from_path(&t.ctx, &src_int).await.unwrap(); assert_eq!(blob.as_name(), "$BLOBDIR/internal"); - let data = fs::read(blob.to_abs_path()).unwrap(); + let data = fs::read(blob.to_abs_path()).await.unwrap(); assert_eq!(data, b"boo"); } #[async_std::test] async fn test_create_from_name_long() { let t = dummy_context().await; let src_ext = t.dir.path().join("autocrypt-setup-message-4137848473.html"); - fs::write(&src_ext, b"boo").unwrap(); - let blob = BlobObject::new_from_path(&t.ctx, &src_ext).unwrap(); + fs::write(&src_ext, b"boo").await.unwrap(); + let blob = BlobObject::new_from_path(&t.ctx, &src_ext).await.unwrap(); assert_eq!( blob.as_name(), "$BLOBDIR/autocrypt-setup-message-4137848473.html" diff --git a/src/chat.rs b/src/chat.rs index 4b4868b7c..97dbf83e3 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -1,9 +1,9 @@ //! # Chat module use std::convert::TryFrom; -use std::path::{Path, PathBuf}; use std::time::{Duration, SystemTime}; +use async_std::path::{Path, PathBuf}; use itertools::Itertools; use num_traits::FromPrimitive; use serde::{Deserialize, Serialize}; @@ -326,7 +326,8 @@ impl ChatId { _ => { let blob = msg .param - .get_blob(Param::File, context, !msg.is_increation())? + .get_blob(Param::File, context, !msg.is_increation()) + .await? .ok_or_else(|| format_err!("No file stored in params"))?; msg.param.set(Param::File, blob.as_name()); } @@ -697,7 +698,8 @@ impl Chat { profile_image: self .get_profile_image(context) .await - .unwrap_or_else(PathBuf::new), + .map(Into::into) + .unwrap_or_else(std::path::PathBuf::new), subtitle: self.get_subtitle(context).await, draft, is_muted: self.is_muted(), @@ -1046,7 +1048,7 @@ pub struct ChatInfo { /// /// If there is no profile image set this will be an empty string /// currently. - pub profile_image: PathBuf, + pub profile_image: std::path::PathBuf, /// Subtitle for the chat. pub subtitle: String, @@ -1158,7 +1160,7 @@ pub(crate) async fn update_saved_messages_icon(context: &Context) -> Result<(), // if there is no saved-messages chat, there is nothing to update. this is no error. if let Ok((chat_id, _)) = lookup_by_contact_id(context, DC_CONTACT_ID_SELF).await { let icon = include_bytes!("../assets/icon-saved-messages.png"); - let blob = BlobObject::create(context, "icon-saved-messages.png".to_string(), icon)?; + let blob = BlobObject::create(context, "icon-saved-messages.png".to_string(), icon).await?; let icon = blob.as_name().to_string(); let mut chat = Chat::load_from_db(context, chat_id).await?; @@ -1172,7 +1174,7 @@ pub(crate) async fn update_device_icon(context: &Context) -> Result<(), Error> { // if there is no device-chat, there is nothing to update. this is no error. if let Ok((chat_id, _)) = lookup_by_contact_id(context, DC_CONTACT_ID_DEVICE).await { let icon = include_bytes!("../assets/icon-device.png"); - let blob = BlobObject::create(context, "icon-device.png".to_string(), icon)?; + let blob = BlobObject::create(context, "icon-device.png".to_string(), icon).await?; let icon = blob.as_name().to_string(); let mut chat = Chat::load_from_db(context, chat_id).await?; @@ -1335,13 +1337,14 @@ pub(crate) fn msgtype_has_file(msgtype: Viewtype) -> bool { } } -fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<(), Error> { +async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<(), Error> { if msg.viewtype == Viewtype::Text { // the caller should check if the message text is empty } else if msgtype_has_file(msg.viewtype) { let blob = msg .param - .get_blob(Param::File, context, !msg.is_increation())? + .get_blob(Param::File, context, !msg.is_increation()) + .await? .ok_or_else(|| { format_err!("Attachment missing for message of type #{}", msg.viewtype) })?; @@ -1383,7 +1386,7 @@ async fn prepare_msg_common( msg: &mut Message, ) -> Result { msg.id = MsgId::new_unset(); - prepare_msg_blob(context, msg)?; + prepare_msg_blob(context, msg).await?; chat_id.unarchive(context).await?; let mut chat = Chat::load_from_db(context, chat_id).await?; @@ -2364,14 +2367,15 @@ pub async fn set_chat_profile_image( .await, ); } else { - let image_blob = BlobObject::from_path(context, Path::new(new_image.as_ref())).or_else( - |err| match err { + let image_blob = match BlobObject::from_path(context, Path::new(new_image.as_ref())) { + Ok(blob) => Ok(blob), + Err(err) => match err { BlobError::WrongBlobdir { .. } => { - BlobObject::create_and_copy(context, Path::new(new_image.as_ref())) + BlobObject::create_and_copy(context, Path::new(new_image.as_ref())).await } _ => Err(err), }, - )?; + }?; image_blob.recode_to_avatar_size(context)?; chat.param.set(Param::ProfileImage, image_blob.as_name()); msg.param.set(Param::Arg, image_blob.as_name()); @@ -2560,7 +2564,7 @@ pub async fn add_device_msg( let rfc724_mid = dc_create_outgoing_rfc724_mid(None, "@device"); msg.try_calc_and_set_dimensions(context).await.ok(); - prepare_msg_blob(context, msg)?; + prepare_msg_blob(context, msg).await?; chat_id.unarchive(context).await?; context.sql.execute( diff --git a/src/config.rs b/src/config.rs index 187d26735..66197c970 100644 --- a/src/config.rs +++ b/src/config.rs @@ -141,7 +141,7 @@ impl Context { .await?; match value { Some(value) => { - let blob = BlobObject::new_from_path(&self, value)?; + let blob = BlobObject::new_from_path(&self, value).await?; blob.recode_to_avatar_size(self)?; self.sql .set_raw_config(self, key, Some(blob.as_name())) @@ -231,12 +231,12 @@ mod tests { .write_all(avatar_bytes) .unwrap(); let avatar_blob = t.ctx.get_blobdir().join("avatar.jpg"); - assert!(!avatar_blob.exists()); + assert!(!avatar_blob.exists().await); t.ctx .set_config(Config::Selfavatar, Some(&avatar_src.to_str().unwrap())) .await .unwrap(); - assert!(avatar_blob.exists()); + assert!(avatar_blob.exists().await); assert!(std::fs::metadata(&avatar_blob).unwrap().len() < avatar_bytes.len() as u64); let avatar_cfg = t.ctx.get_config(Config::Selfavatar).await; assert_eq!(avatar_cfg, avatar_blob.to_str().map(|s| s.to_string())); diff --git a/src/contact.rs b/src/contact.rs index 2453c1f91..af8a5855e 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -1,7 +1,6 @@ //! Contacts module -use std::path::PathBuf; - +use async_std::path::PathBuf; use deltachat_derive::*; use itertools::Itertools; use rusqlite; diff --git a/src/context.rs b/src/context.rs index 7a7408f63..cba9d15f4 100644 --- a/src/context.rs +++ b/src/context.rs @@ -2,9 +2,9 @@ use std::collections::HashMap; use std::ffi::OsString; -use std::path::{Path, PathBuf}; use std::sync::atomic::AtomicBool; +use async_std::path::{Path, PathBuf}; use async_std::sync::{Arc, Mutex, RwLock}; use crate::chat::*; @@ -94,7 +94,7 @@ impl Context { blob_fname.push(dbfile.file_name().unwrap_or_default()); blob_fname.push("-blobs"); let blobdir = dbfile.with_file_name(blob_fname); - if !blobdir.exists() { + if !blobdir.exists().await { std::fs::create_dir_all(&blobdir)?; } Context::with_blobdir(cb, os_name, dbfile, blobdir).await @@ -107,7 +107,7 @@ impl Context { blobdir: PathBuf, ) -> Result { ensure!( - blobdir.is_dir(), + blobdir.is_dir().await, "Blobdir does not exist: {}", blobdir.display() ); @@ -518,7 +518,7 @@ mod tests { let tmp = tempfile::tempdir().unwrap(); let dbfile = tmp.path().join("db.sqlite"); std::fs::write(&dbfile, b"123").unwrap(); - let res = Context::new(Box::new(|_, _| ()), "FakeOs".into(), dbfile).await; + let res = Context::new(Box::new(|_, _| ()), "FakeOs".into(), dbfile.into()).await; assert!(res.is_err()); } @@ -533,7 +533,7 @@ mod tests { async fn test_blobdir_exists() { let tmp = tempfile::tempdir().unwrap(); let dbfile = tmp.path().join("db.sqlite"); - Context::new(Box::new(|_, _| ()), "FakeOS".into(), dbfile) + Context::new(Box::new(|_, _| ()), "FakeOS".into(), dbfile.into()) .await .unwrap(); let blobdir = tmp.path().join("db.sqlite-blobs"); @@ -546,7 +546,7 @@ mod tests { let dbfile = tmp.path().join("db.sqlite"); let blobdir = tmp.path().join("db.sqlite-blobs"); std::fs::write(&blobdir, b"123").unwrap(); - let res = Context::new(Box::new(|_, _| ()), "FakeOS".into(), dbfile).await; + let res = Context::new(Box::new(|_, _| ()), "FakeOS".into(), dbfile.into()).await; assert!(res.is_err()); } @@ -556,7 +556,7 @@ mod tests { let subdir = tmp.path().join("subdir"); let dbfile = subdir.join("db.sqlite"); let dbfile2 = dbfile.clone(); - Context::new(Box::new(|_, _| ()), "FakeOS".into(), dbfile) + Context::new(Box::new(|_, _| ()), "FakeOS".into(), dbfile.into()) .await .unwrap(); assert!(subdir.is_dir()); @@ -568,8 +568,13 @@ mod tests { let tmp = tempfile::tempdir().unwrap(); let dbfile = tmp.path().join("db.sqlite"); let blobdir = PathBuf::new(); - let res = - Context::with_blobdir(Box::new(|_, _| ()), "FakeOS".into(), dbfile, blobdir).await; + let res = Context::with_blobdir( + Box::new(|_, _| ()), + "FakeOS".into(), + dbfile.into(), + blobdir.into(), + ) + .await; assert!(res.is_err()); } @@ -578,8 +583,13 @@ mod tests { let tmp = tempfile::tempdir().unwrap(); let dbfile = tmp.path().join("db.sqlite"); let blobdir = tmp.path().join("blobs"); - let res = - Context::with_blobdir(Box::new(|_, _| ()), "FakeOS".into(), dbfile, blobdir).await; + let res = Context::with_blobdir( + Box::new(|_, _| ()), + "FakeOS".into(), + dbfile.into(), + blobdir.into(), + ) + .await; assert!(res.is_err()); } diff --git a/src/dc_tools.rs b/src/dc_tools.rs index 1144a2b57..04eeed66c 100644 --- a/src/dc_tools.rs +++ b/src/dc_tools.rs @@ -3,11 +3,12 @@ use core::cmp::{max, min}; use std::borrow::Cow; -use std::path::{Path, PathBuf}; +use std::fmt; use std::str::FromStr; use std::time::SystemTime; -use std::{fmt, fs}; +use async_std::path::{Path, PathBuf}; +use async_std::{fs, io}; use chrono::{Local, TimeZone}; use rand::{thread_rng, Rng}; @@ -240,11 +241,8 @@ pub fn dc_get_filemeta(buf: &[u8]) -> Result<(u32, u32), Error> { /// /// If `path` starts with "$BLOBDIR", replaces it with the blobdir path. /// Otherwise, returns path as is. -pub(crate) fn dc_get_abs_path>( - context: &Context, - path: P, -) -> std::path::PathBuf { - let p: &std::path::Path = path.as_ref(); +pub(crate) fn dc_get_abs_path>(context: &Context, path: P) -> PathBuf { + let p: &Path = path.as_ref(); if let Ok(p) = p.strip_prefix("$BLOBDIR") { context.get_blobdir().join(p) } else { @@ -252,20 +250,20 @@ pub(crate) fn dc_get_abs_path>( } } -pub(crate) fn dc_get_filebytes(context: &Context, path: impl AsRef) -> u64 { +pub(crate) async fn dc_get_filebytes(context: &Context, path: impl AsRef) -> u64 { let path_abs = dc_get_abs_path(context, &path); - match fs::metadata(&path_abs) { + match fs::metadata(&path_abs).await { Ok(meta) => meta.len() as u64, Err(_err) => 0, } } -pub(crate) fn dc_delete_file(context: &Context, path: impl AsRef) -> bool { +pub(crate) async fn dc_delete_file(context: &Context, path: impl AsRef) -> bool { let path_abs = dc_get_abs_path(context, &path); - if !path_abs.exists() { + if !path_abs.exists().await { return false; } - if !path_abs.is_file() { + if !path_abs.is_file().await { warn!( context, "refusing to delete non-file \"{}\".", @@ -275,7 +273,7 @@ pub(crate) fn dc_delete_file(context: &Context, path: impl AsRef { context.call_cb(Event::DeletedBlobFile(dpath)); true @@ -287,13 +285,13 @@ pub(crate) fn dc_delete_file(context: &Context, path: impl AsRef, - dest_path: impl AsRef, + src_path: impl AsRef, + dest_path: impl AsRef, ) -> bool { let src_abs = dc_get_abs_path(context, &src_path); - let mut src_file = match fs::File::open(&src_abs) { + let mut src_file = match fs::File::open(&src_abs).await { Ok(file) => file, Err(err) => { warn!( @@ -311,6 +309,7 @@ pub(crate) fn dc_copy_file( .create_new(true) .write(true) .open(&dest_abs) + .await { Ok(file) => file, Err(err) => { @@ -324,7 +323,7 @@ pub(crate) fn dc_copy_file( } }; - match std::io::copy(&mut src_file, &mut dest_file) { + match io::copy(&mut src_file, &mut dest_file).await { Ok(_) => true, Err(err) => { error!( @@ -336,20 +335,20 @@ pub(crate) fn dc_copy_file( ); { // Attempt to remove the failed file, swallow errors resulting from that. - fs::remove_file(dest_abs).ok(); + fs::remove_file(dest_abs).await.ok(); } false } } } -pub(crate) fn dc_create_folder( +pub(crate) async fn dc_create_folder( context: &Context, - path: impl AsRef, -) -> Result<(), std::io::Error> { + path: impl AsRef, +) -> Result<(), io::Error> { let path_abs = dc_get_abs_path(context, &path); - if !path_abs.exists() { - match fs::create_dir_all(path_abs) { + if !path_abs.exists().await { + match fs::create_dir_all(path_abs).await { Ok(_) => Ok(()), Err(err) => { warn!( @@ -367,13 +366,13 @@ pub(crate) fn dc_create_folder( } /// Write a the given content to provied file path. -pub(crate) fn dc_write_file( +pub(crate) async fn dc_write_file( context: &Context, path: impl AsRef, buf: &[u8], -) -> Result<(), std::io::Error> { +) -> Result<(), io::Error> { let path_abs = dc_get_abs_path(context, &path); - fs::write(&path_abs, buf).map_err(|err| { + fs::write(&path_abs, buf).await.map_err(|err| { warn!( context, "Cannot write {} bytes to \"{}\": {}", @@ -385,13 +384,10 @@ pub(crate) fn dc_write_file( }) } -pub fn dc_read_file>( - context: &Context, - path: P, -) -> Result, Error> { +pub async fn dc_read_file>(context: &Context, path: P) -> Result, Error> { let path_abs = dc_get_abs_path(context, &path); - match fs::read(&path_abs) { + match fs::read(&path_abs).await { Ok(bytes) => Ok(bytes), Err(err) => { warn!( @@ -405,13 +401,31 @@ pub fn dc_read_file>( } } -pub fn dc_open_file>( +pub async fn dc_open_file>(context: &Context, path: P) -> Result { + let path_abs = dc_get_abs_path(context, &path); + + match fs::File::open(&path_abs).await { + Ok(bytes) => Ok(bytes), + Err(err) => { + warn!( + context, + "Cannot read \"{}\" or file is empty: {}", + path.as_ref().display(), + err + ); + Err(err.into()) + } + } +} + +pub fn dc_open_file_std>( context: &Context, path: P, ) -> Result { - let path_abs = dc_get_abs_path(context, &path); + let p: PathBuf = path.as_ref().into(); + let path_abs = dc_get_abs_path(context, p); - match fs::File::open(&path_abs) { + match std::fs::File::open(&path_abs) { Ok(bytes) => Ok(bytes), Err(err) => { warn!( @@ -425,7 +439,7 @@ pub fn dc_open_file>( } } -pub(crate) fn dc_get_next_backup_path( +pub(crate) async fn dc_get_next_backup_path( folder: impl AsRef, backup_time: i64, ) -> Result { @@ -438,7 +452,7 @@ pub(crate) fn dc_get_next_backup_path( for i in 0..64 { let mut path = folder.clone(); path.push(format!("{}-{}.bak", stem, i)); - if !path.exists() { + if !path.exists().await { return Ok(path); } } @@ -715,27 +729,31 @@ mod tests { async fn test_file_handling() { let t = dummy_context().await; let context = &t.ctx; - let dc_file_exist = |ctx: &Context, fname: &str| { - ctx.get_blobdir() - .join(Path::new(fname).file_name().unwrap()) - .exists() - }; - - assert!(!dc_delete_file(context, "$BLOBDIR/lkqwjelqkwlje")); - if dc_file_exist(context, "$BLOBDIR/foobar") - || dc_file_exist(context, "$BLOBDIR/dada") - || dc_file_exist(context, "$BLOBDIR/foobar.dadada") - || dc_file_exist(context, "$BLOBDIR/foobar-folder") - { - dc_delete_file(context, "$BLOBDIR/foobar"); - dc_delete_file(context, "$BLOBDIR/dada"); - dc_delete_file(context, "$BLOBDIR/foobar.dadada"); - dc_delete_file(context, "$BLOBDIR/foobar-folder"); + macro_rules! dc_file_exist { + ($ctx:expr, $fname:expr) => { + $ctx.get_blobdir() + .join(Path::new($fname).file_name().unwrap()) + .exists() + }; } - assert!(dc_write_file(context, "$BLOBDIR/foobar", b"content").is_ok()); - assert!(dc_file_exist(context, "$BLOBDIR/foobar",)); - assert!(!dc_file_exist(context, "$BLOBDIR/foobarx")); - assert_eq!(dc_get_filebytes(context, "$BLOBDIR/foobar"), 7); + + assert!(!dc_delete_file(context, "$BLOBDIR/lkqwjelqkwlje").await); + if dc_file_exist!(context, "$BLOBDIR/foobar").await + || dc_file_exist!(context, "$BLOBDIR/dada").await + || dc_file_exist!(context, "$BLOBDIR/foobar.dadada").await + || dc_file_exist!(context, "$BLOBDIR/foobar-folder").await + { + dc_delete_file(context, "$BLOBDIR/foobar").await; + dc_delete_file(context, "$BLOBDIR/dada").await; + dc_delete_file(context, "$BLOBDIR/foobar.dadada").await; + dc_delete_file(context, "$BLOBDIR/foobar-folder").await; + } + assert!(dc_write_file(context, "$BLOBDIR/foobar", b"content") + .await + .is_ok()); + assert!(dc_file_exist!(context, "$BLOBDIR/foobar").await); + assert!(!dc_file_exist!(context, "$BLOBDIR/foobarx").await); + assert_eq!(dc_get_filebytes(context, "$BLOBDIR/foobar").await, 7); let abs_path = context .get_blobdir() @@ -743,31 +761,33 @@ mod tests { .to_string_lossy() .to_string(); - assert!(dc_file_exist(context, &abs_path)); + assert!(dc_file_exist!(context, &abs_path).await); - assert!(dc_copy_file(context, "$BLOBDIR/foobar", "$BLOBDIR/dada",)); + assert!(dc_copy_file(context, "$BLOBDIR/foobar", "$BLOBDIR/dada").await); // attempting to copy a second time should fail - assert!(!dc_copy_file(context, "$BLOBDIR/foobar", "$BLOBDIR/dada",)); + assert!(!dc_copy_file(context, "$BLOBDIR/foobar", "$BLOBDIR/dada").await); - assert_eq!(dc_get_filebytes(context, "$BLOBDIR/dada",), 7); + assert_eq!(dc_get_filebytes(context, "$BLOBDIR/dada").await, 7); - let buf = dc_read_file(context, "$BLOBDIR/dada").unwrap(); + let buf = dc_read_file(context, "$BLOBDIR/dada").await.unwrap(); assert_eq!(buf.len(), 7); assert_eq!(&buf, b"content"); - assert!(dc_delete_file(context, "$BLOBDIR/foobar")); - assert!(dc_delete_file(context, "$BLOBDIR/dada")); - assert!(dc_create_folder(context, "$BLOBDIR/foobar-folder").is_ok()); - assert!(dc_file_exist(context, "$BLOBDIR/foobar-folder",)); - assert!(!dc_delete_file(context, "$BLOBDIR/foobar-folder")); + assert!(dc_delete_file(context, "$BLOBDIR/foobar").await); + assert!(dc_delete_file(context, "$BLOBDIR/dada").await); + assert!(dc_create_folder(context, "$BLOBDIR/foobar-folder") + .await + .is_ok()); + assert!(dc_file_exist!(context, "$BLOBDIR/foobar-folder").await); + assert!(!dc_delete_file(context, "$BLOBDIR/foobar-folder").await); let fn0 = "$BLOBDIR/data.data"; - assert!(dc_write_file(context, &fn0, b"content").is_ok()); + assert!(dc_write_file(context, &fn0, b"content").await.is_ok()); - assert!(dc_delete_file(context, &fn0)); - assert!(!dc_file_exist(context, &fn0)); + assert!(dc_delete_file(context, &fn0).await); + assert!(!dc_file_exist!(context, &fn0).await); } #[test] diff --git a/src/events.rs b/src/events.rs index 37a0a4bc4..4d7e17ab9 100644 --- a/src/events.rs +++ b/src/events.rs @@ -1,6 +1,6 @@ //! # Events specification -use std::path::PathBuf; +use async_std::path::PathBuf; use strum::EnumProperty; diff --git a/src/imex.rs b/src/imex.rs index 3ca1a2a89..c5f82b97e 100644 --- a/src/imex.rs +++ b/src/imex.rs @@ -1,8 +1,9 @@ //! # Import/export module -use core::cmp::{max, min}; -use std::path::Path; +use std::cmp::{max, min}; +use async_std::path::{Path, PathBuf}; +use async_std::prelude::*; use num_traits::FromPrimitive; use rand::{thread_rng, Rng}; @@ -83,10 +84,10 @@ pub async fn imex(context: &Context, what: ImexMode, param1: Option) -> Result { let dir_name = dir_name.as_ref(); - let dir_iter = std::fs::read_dir(dir_name)?; + let mut dir_iter = async_std::fs::read_dir(dir_name).await?; let mut newest_backup_time = 0; - let mut newest_backup_path: Option = None; - for dirent in dir_iter { + let mut newest_backup_path: Option = None; + while let Some(dirent) = dir_iter.next().await { if let Ok(dirent) = dirent { let path = dirent.path(); let name = dirent.file_name(); @@ -133,7 +134,8 @@ async fn do_initiate_key_transfer(context: &Context) -> Result { context, "autocrypt-setup-message.html", setup_file_content.as_bytes(), - )?; + ) + .await?; let chat_id = chat::create_by_contact_id(context, DC_CONTACT_ID_SELF).await?; msg = Message::default(); @@ -270,7 +272,7 @@ pub async fn continue_key_transfer( ); if let Some(filename) = msg.get_file(context) { - let file = dc_open_file(context, filename)?; + let file = dc_open_file_std(context, filename)?; let sc = normalize_setup_code(setup_code); let armored_key = decrypt_setup_file(context, &sc, file)?; set_self_key(context, &armored_key, true, true).await?; @@ -383,7 +385,7 @@ pub async fn job_imex_imap(context: &Context, job: &Job) -> Result<()> { context.free_ongoing().await; bail!("Cannot create private key or private key not available."); } else { - dc_create_folder(context, ¶m)?; + dc_create_folder(context, ¶m).await?; } } let path = Path::new(param); @@ -424,14 +426,14 @@ async fn import_backup(context: &Context, backup_to_import: impl AsRef) -> "Cannot import backups to accounts in use." ); context.sql.close(&context).await; - dc_delete_file(context, context.get_dbfile()); + dc_delete_file(context, context.get_dbfile()).await; ensure!( - !context.get_dbfile().exists(), + !context.get_dbfile().exists().await, "Cannot delete old database." ); ensure!( - dc_copy_file(context, backup_to_import.as_ref(), context.get_dbfile()), + dc_copy_file(context, backup_to_import.as_ref(), context.get_dbfile()).await, "could not copy file" ); /* error already logged */ @@ -494,7 +496,7 @@ async fn import_backup(context: &Context, backup_to_import: impl AsRef) -> } let path_filename = context.get_blobdir().join(file_name); - dc_write_file(context, &path_filename, &file_blob)?; + dc_write_file(context, &path_filename, &file_blob).await?; } if all_files_extracted { @@ -520,7 +522,7 @@ async fn export_backup(context: &Context, dir: impl AsRef) -> Result<()> { // FIXME: we should write to a temporary file first and rename it on success. this would guarantee the backup is complete. // let dest_path_filename = dc_get_next_backup_file(context, dir, res); let now = time(); - let dest_path_filename = dc_get_next_backup_path(dir, now)?; + let dest_path_filename = dc_get_next_backup_path(dir, now).await?; let dest_path_string = dest_path_filename.to_string_lossy().to_string(); sql::housekeeping(context).await; @@ -535,7 +537,7 @@ async fn export_backup(context: &Context, dir: impl AsRef) -> Result<()> { context.get_dbfile().display(), dest_path_filename.display(), ); - let copied = dc_copy_file(context, context.get_dbfile(), &dest_path_filename); + let copied = dc_copy_file(context, context.get_dbfile(), &dest_path_filename).await; context .sql .open(&context, &context.get_dbfile(), false) @@ -556,7 +558,7 @@ async fn export_backup(context: &Context, dir: impl AsRef) -> Result<()> { ); let res = match add_files_to_export(context, &dest_sql).await { Err(err) => { - dc_delete_file(context, &dest_path_filename); + dc_delete_file(context, &dest_path_filename).await; error!(context, "backup failed: {}", err); Err(err) } @@ -586,17 +588,17 @@ async fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> { // copy all files from BLOBDIR into backup-db let mut total_files_cnt = 0; let dir = context.get_blobdir(); - let dir_handle = std::fs::read_dir(&dir)?; - total_files_cnt += dir_handle.filter(|r| r.is_ok()).count(); + let dir_handle = async_std::fs::read_dir(&dir).await?; + total_files_cnt += dir_handle.filter(|r| r.is_ok()).count().await; info!(context, "EXPORT: total_files_cnt={}", total_files_cnt); sql.with_conn_async(|conn| async move { // scan directory, pass 2: copy files - let dir_handle = std::fs::read_dir(&dir)?; + let mut dir_handle = async_std::fs::read_dir(&dir).await?; let mut processed_files_cnt = 0; - for entry in dir_handle { + while let Some(entry) = dir_handle.next().await { let entry = entry?; if context.shall_stop_ongoing().await { return Ok(()); @@ -612,7 +614,7 @@ async fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> { } info!(context, "EXPORT: copying filename={}", name); let curr_path_filename = context.get_blobdir().join(entry.file_name()); - if let Ok(buf) = dc_read_file(context, &curr_path_filename) { + if let Ok(buf) = dc_read_file(context, &curr_path_filename).await { if buf.is_empty() { continue; } @@ -644,8 +646,8 @@ async fn import_self_keys(context: &Context, dir: impl AsRef) -> Result<() let mut imported_cnt = 0; let dir_name = dir.as_ref().to_string_lossy(); - let dir_handle = std::fs::read_dir(&dir)?; - for entry in dir_handle { + let mut dir_handle = async_std::fs::read_dir(&dir).await?; + while let Some(entry) = dir_handle.next().await { let entry_fn = entry?.file_name(); let name_f = entry_fn.to_string_lossy(); let path_plus_name = dir.as_ref().join(&entry_fn); @@ -665,7 +667,7 @@ async fn import_self_keys(context: &Context, dir: impl AsRef) -> Result<() continue; } } - match dc_read_file(context, &path_plus_name) { + match dc_read_file(context, &path_plus_name).await { Ok(buf) => { let armored = std::string::String::from_utf8_lossy(&buf); if let Err(err) = set_self_key(context, &armored, set_default, false).await { @@ -688,7 +690,7 @@ async fn import_self_keys(context: &Context, dir: impl AsRef) -> Result<() async fn export_self_keys(context: &Context, dir: impl AsRef) -> Result<()> { let mut export_errors = 0; - context + let keys = context .sql .query_map( "SELECT id, public_key, private_key, is_default FROM keypairs;", @@ -704,30 +706,36 @@ async fn export_self_keys(context: &Context, dir: impl AsRef) -> Result<() Ok((id, public_key, private_key, is_default)) }, |keys| { - for key_pair in keys { - let (id, public_key, private_key, is_default) = key_pair?; - let id = Some(id).filter(|_| is_default != 0); - if let Some(key) = public_key { - if export_key_to_asc_file(context, &dir, id, &key).is_err() { - export_errors += 1; - } - } else { - export_errors += 1; - } - if let Some(key) = private_key { - if export_key_to_asc_file(context, &dir, id, &key).is_err() { - export_errors += 1; - } - } else { - export_errors += 1; - } - } - - Ok(()) + keys.collect::, _>>() + .map_err(Into::into) }, ) .await?; + for (id, public_key, private_key, is_default) in keys { + let id = Some(id).filter(|_| is_default != 0); + if let Some(key) = public_key { + if export_key_to_asc_file(context, &dir, id, &key) + .await + .is_err() + { + export_errors += 1; + } + } else { + export_errors += 1; + } + if let Some(key) = private_key { + if export_key_to_asc_file(context, &dir, id, &key) + .await + .is_err() + { + export_errors += 1; + } + } else { + export_errors += 1; + } + } + ensure!(export_errors == 0, "errors while exporting keys"); Ok(()) } @@ -735,7 +743,7 @@ async fn export_self_keys(context: &Context, dir: impl AsRef) -> Result<() /******************************************************************************* * Classic key export ******************************************************************************/ -fn export_key_to_asc_file( +async fn export_key_to_asc_file( context: &Context, dir: impl AsRef, id: Option, @@ -748,9 +756,9 @@ fn export_key_to_asc_file( dir.as_ref().join(format!("{}-key-{}.asc", kind, &id)) }; info!(context, "Exporting key {}", file_name.display()); - dc_delete_file(context, &file_name); + dc_delete_file(context, &file_name).await; - let res = key.write_asc_to_file(&file_name, context); + let res = key.write_asc_to_file(&file_name, context).await; if res.is_err() { error!(context, "Cannot write key to {}", file_name.display()); } else { @@ -819,10 +827,12 @@ mod tests { let context = dummy_context().await; let key = Key::from(alice_keypair().public); let blobdir = "$BLOBDIR"; - assert!(export_key_to_asc_file(&context.ctx, blobdir, None, &key).is_ok()); + assert!(export_key_to_asc_file(&context.ctx, blobdir, None, &key) + .await + .is_ok()); let blobdir = context.ctx.get_blobdir().to_str().unwrap(); let filename = format!("{}/public-key-default.asc", blobdir); - let bytes = std::fs::read(&filename).unwrap(); + let bytes = async_std::fs::read(&filename).await.unwrap(); assert_eq!(bytes, key.to_asc(None).into_bytes()); } diff --git a/src/job.rs b/src/job.rs index 1274ce946..edc9c2342 100644 --- a/src/job.rs +++ b/src/job.rs @@ -261,7 +261,7 @@ impl Job { .get_path(Param::File, context) .map_err(|_| format_err!("Can't get filename"))) .ok_or_else(|| format_err!("Can't get filename"))); - let body = job_try!(dc_read_file(context, &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") @@ -298,7 +298,7 @@ impl Job { set_delivered(context, MsgId::new(foreign_id)).await; } // now also delete the generated file - dc_delete_file(context, filename); + dc_delete_file(context, filename).await; Ok(()) } }) @@ -1086,7 +1086,7 @@ async fn add_smtp_job( ensure!(!recipients.is_empty(), "no recipients for smtp job set"); let mut param = Params::new(); let bytes = &rendered_msg.message; - let blob = BlobObject::create(context, &rendered_msg.rfc724_mid, bytes)?; + let blob = BlobObject::create(context, &rendered_msg.rfc724_mid, bytes).await?; let recipients = recipients.join("\x1e"); param.set(Param::File, blob.as_name()); diff --git a/src/key.rs b/src/key.rs index 0ab6bcc23..7e1800566 100644 --- a/src/key.rs +++ b/src/key.rs @@ -2,8 +2,8 @@ use std::collections::BTreeMap; use std::io::Cursor; -use std::path::Path; +use async_std::path::Path; use pgp::composed::Deserializable; use pgp::ser::Serialize; use pgp::types::{KeyTrait, SecretKeyTrait}; @@ -268,14 +268,14 @@ impl Key { .expect("failed to serialize key") } - pub fn write_asc_to_file( + pub async fn write_asc_to_file( &self, file: impl AsRef, context: &Context, ) -> std::io::Result<()> { let file_content = self.to_asc(None).into_bytes(); - let res = dc_write_file(context, &file, &file_content); + let res = dc_write_file(context, &file, &file_content).await; if res.is_err() { error!(context, "Cannot write key to {}", file.as_ref().display()); } diff --git a/src/message.rs b/src/message.rs index e1e874667..9f9ad4631 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1,7 +1,6 @@ //! # Messages and their identifiers -use std::path::{Path, PathBuf}; - +use async_std::path::{Path, PathBuf}; use deltachat_derive::{FromSql, ToSql}; use failure::Fail; use lazy_static::lazy_static; @@ -349,7 +348,7 @@ impl Message { self.param.set_int(Param::Width, 0); self.param.set_int(Param::Height, 0); - if let Ok(buf) = dc_read_file(context, path_and_filename) { + if let Ok(buf) = dc_read_file(context, path_and_filename).await { if let Ok((width, height)) = dc_get_filemeta(&buf) { self.param.set_int(Param::Width, width as i32); self.param.set_int(Param::Height, height as i32); @@ -450,12 +449,12 @@ impl Message { .map(|name| name.to_string_lossy().to_string()) } - pub fn get_filebytes(&self, context: &Context) -> u64 { - self.param - .get_path(Param::File, context) - .unwrap_or(None) - .map(|path| dc_get_filebytes(context, &path)) - .unwrap_or_default() + pub async fn get_filebytes(&self, context: &Context) -> u64 { + match self.param.get_path(Param::File, context) { + Ok(Some(path)) => dc_get_filebytes(context, &path).await, + Ok(None) => 0, + Err(_) => 0, + } } pub fn get_width(&self) -> i32 { @@ -558,13 +557,13 @@ impl Message { self.param.get_cmd() == SystemMessage::AutocryptSetupMessage } - pub fn get_setupcodebegin(&self, context: &Context) -> Option { + pub async fn get_setupcodebegin(&self, context: &Context) -> Option { if !self.is_setupmessage() { return None; } if let Some(filename) = self.get_file(context) { - if let Ok(ref buf) = dc_read_file(context, filename) { + if let Ok(ref buf) = dc_read_file(context, filename).await { if let Ok((typ, headers, _)) = split_armored_data(buf) { if typ == pgp::armor::BlockType::Message { return headers.get(crate::pgp::HEADER_SETUPCODE).cloned(); @@ -909,7 +908,7 @@ pub async fn get_msg_info(context: &Context, msg_id: MsgId) -> String { } if let Some(path) = msg.get_file(context) { - let bytes = dc_get_filebytes(context, &path); + let bytes = dc_get_filebytes(context, &path).await; ret += &format!("\nFile: {}, {}, bytes\n", path.display(), bytes); } diff --git a/src/mimefactory.rs b/src/mimefactory.rs index b831f7c7b..a541b5997 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -808,7 +808,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> { meta.viewtype = Viewtype::Image; meta.param.set(Param::File, grpimage); - let (mail, filename_as_sent) = build_body_file(context, &meta, "group-image")?; + let (mail, filename_as_sent) = build_body_file(context, &meta, "group-image").await?; meta_part = Some(mail); protected_headers.push(Header::new("Chat-Group-Avatar".into(), filename_as_sent)); } @@ -879,13 +879,13 @@ impl<'a, 'b> MimeFactory<'a, 'b> { // add attachment part if chat::msgtype_has_file(self.msg.viewtype) { - if !is_file_size_okay(context, &self.msg) { + if !is_file_size_okay(context, &self.msg).await { bail!( "Message exceeds the recommended {} MB.", RECOMMENDED_FILE_SIZE / 1_000_000, ); } else { - let (file_part, _) = build_body_file(context, &self.msg, "")?; + let (file_part, _) = build_body_file(context, &self.msg, "").await?; parts.push(file_part); } } @@ -1035,14 +1035,15 @@ fn wrapped_base64_encode(buf: &[u8]) -> String { .join("\r\n") } -fn build_body_file( +async fn build_body_file( context: &Context, msg: &Message, base_name: &str, ) -> Result<(PartBuilder, String), Error> { let blob = msg .param - .get_blob(Param::File, context, true)? + .get_blob(Param::File, context, true) + .await? .ok_or_else(|| format_err!("msg has no filename"))?; let suffix = blob.suffix().unwrap_or("dat"); @@ -1138,10 +1139,10 @@ fn recipients_contain_addr(recipients: &[(String, String)], addr: &str) -> bool .any(|(_, cur)| cur.to_lowercase() == addr_lc) } -fn is_file_size_okay(context: &Context, msg: &Message) -> bool { +async fn is_file_size_okay(context: &Context, msg: &Message) -> bool { match msg.param.get_path(Param::File, context).unwrap_or(None) { Some(path) => { - let bytes = dc_get_filebytes(context, &path); + let bytes = dc_get_filebytes(context, &path).await; bytes <= UPPER_LIMIT_FILE_SIZE } None => false, diff --git a/src/mimeparser.rs b/src/mimeparser.rs index 0836ff6bf..707d7afeb 100644 --- a/src/mimeparser.rs +++ b/src/mimeparser.rs @@ -463,7 +463,7 @@ impl MimeMessage { self.parse_mime_recursive(context, &mail).await } - MimeS::Single => self.add_single_part_if_known(context, mail), + MimeS::Single => self.add_single_part_if_known(context, mail).await, } } .boxed() @@ -578,7 +578,7 @@ impl MimeMessage { Ok(any_part_added) } - fn add_single_part_if_known( + async fn add_single_part_if_known( &mut self, context: &Context, mail: &mailparse::ParsedMail<'_>, @@ -600,7 +600,8 @@ impl MimeMessage { &raw_mime, &mail.get_body_raw()?, &filename, - ); + ) + .await; } None => { match mime_type.type_() { @@ -652,7 +653,7 @@ impl MimeMessage { Ok(self.parts.len() > old_part_count) } - fn do_add_single_file_part( + async fn do_add_single_file_part( &mut self, context: &Context, msg_type: Viewtype, @@ -685,7 +686,7 @@ impl MimeMessage { /* we have a regular file attachment, write decoded data to new blob object */ - let blob = match BlobObject::create(context, filename, decoded_data) { + let blob = match BlobObject::create(context, filename, decoded_data).await { Ok(blob) => blob, Err(err) => { error!( diff --git a/src/param.rs b/src/param.rs index ccd02549f..2b64cd347 100644 --- a/src/param.rs +++ b/src/param.rs @@ -1,8 +1,8 @@ use std::collections::BTreeMap; use std::fmt; -use std::path::PathBuf; use std::str; +use async_std::path::PathBuf; use num_traits::FromPrimitive; use serde::{Deserialize, Serialize}; @@ -281,7 +281,7 @@ impl Params { /// created without copying if the path already referes to a valid /// blob. If so a [BlobObject] will be returned regardless of the /// `create` argument. - pub fn get_blob<'a>( + pub async fn get_blob<'a>( &self, key: Param, context: &'a Context, @@ -294,7 +294,7 @@ impl Params { let file = ParamsFile::from_param(context, val)?; let blob = match file { ParamsFile::FsPath(path) => match create { - true => BlobObject::new_from_path(context, path)?, + true => BlobObject::new_from_path(context, path).await?, false => BlobObject::from_path(context, path)?, }, ParamsFile::Blob(blob) => blob, @@ -368,8 +368,8 @@ impl<'a> ParamsFile<'a> { mod tests { use super::*; - use std::fs; - use std::path::Path; + use async_std::fs; + use async_std::path::Path; use crate::test_utils::*; @@ -446,20 +446,25 @@ mod tests { p.set(Param::File, fname.to_str().unwrap()); let file = p.get_file(Param::File, &t.ctx).unwrap().unwrap(); - assert_eq!(file, ParamsFile::FsPath(fname.clone())); + assert_eq!(file, ParamsFile::FsPath(fname.clone().into())); - let path = p.get_path(Param::File, &t.ctx).unwrap().unwrap(); + let path: PathBuf = p.get_path(Param::File, &t.ctx).unwrap().unwrap(); + let fname: PathBuf = fname.into(); assert_eq!(path, fname); // Blob does not exist yet, expect BlobError. - let err = p.get_blob(Param::File, &t.ctx, false).unwrap_err(); + let err = p.get_blob(Param::File, &t.ctx, false).await.unwrap_err(); match err { BlobError::WrongBlobdir { .. } => (), _ => panic!("wrong error type/variant: {:?}", err), } - fs::write(fname, b"boo").unwrap(); - let blob = p.get_blob(Param::File, &t.ctx, true).unwrap().unwrap(); + fs::write(fname, b"boo").await.unwrap(); + let blob = p + .get_blob(Param::File, &t.ctx, true) + .await + .unwrap() + .unwrap(); assert_eq!( blob, BlobObject::from_name(&t.ctx, "foo".to_string()).unwrap() @@ -468,7 +473,11 @@ mod tests { // Blob in blobdir, expect blob. let bar = t.ctx.get_blobdir().join("bar"); p.set(Param::File, bar.to_str().unwrap()); - let blob = p.get_blob(Param::File, &t.ctx, false).unwrap().unwrap(); + let blob = p + .get_blob(Param::File, &t.ctx, false) + .await + .unwrap() + .unwrap(); assert_eq!( blob, BlobObject::from_name(&t.ctx, "bar".to_string()).unwrap() @@ -477,6 +486,10 @@ mod tests { p.remove(Param::File); assert!(p.get_file(Param::File, &t.ctx).unwrap().is_none()); assert!(p.get_path(Param::File, &t.ctx).unwrap().is_none()); - assert!(p.get_blob(Param::File, &t.ctx, false).unwrap().is_none()); + assert!(p + .get_blob(Param::File, &t.ctx, false) + .await + .unwrap() + .is_none()); } } diff --git a/src/sql.rs b/src/sql.rs index b32d6dfbb..d21f18886 100644 --- a/src/sql.rs +++ b/src/sql.rs @@ -96,7 +96,7 @@ impl Sql { } // return true on success, false on failure - pub async fn open(&self, context: &Context, dbfile: &Path, readonly: bool) -> bool { + pub async fn open>(&self, context: &Context, dbfile: T, readonly: bool) -> bool { match open(context, self, dbfile, readonly).await { Ok(_) => true, Err(crate::error::Error::SqlError(Error::SqlAlreadyOpen)) => false, @@ -607,7 +607,7 @@ pub async fn housekeeping(context: &Context) { entry.file_name() ); let path = entry.path(); - dc_delete_file(context, path); + dc_delete_file(context, path).await; } } Err(err) => { diff --git a/src/stock.rs b/src/stock.rs index 0e30fc794..6cb6bf3e1 100644 --- a/src/stock.rs +++ b/src/stock.rs @@ -376,7 +376,7 @@ impl Context { chat::add_device_msg(&self, Some("core-about-device-chat"), Some(&mut msg)).await?; let image = include_bytes!("../assets/welcome-image.jpg"); - let blob = BlobObject::create(&self, "welcome-image.jpg".to_string(), image)?; + let blob = BlobObject::create(&self, "welcome-image.jpg".to_string(), image).await?; let mut msg = Message::new(Viewtype::Image); msg.param.set(Param::File, blob.as_name()); chat::add_device_msg(&self, Some("core-welcome-image"), Some(&mut msg)).await?; diff --git a/src/test_utils.rs b/src/test_utils.rs index f384df70f..86c61adf2 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -32,7 +32,9 @@ pub(crate) async fn test_context(callback: Option>) -> Test Some(cb) => cb, None => Box::new(|_, _| ()), }; - let ctx = Context::new(cb, "FakeOs".into(), dbfile).await.unwrap(); + let ctx = Context::new(cb, "FakeOs".into(), dbfile.into()) + .await + .unwrap(); TestContext { ctx, dir } } diff --git a/tests/stress.rs b/tests/stress.rs index e4ec5e873..f44aa1e19 100644 --- a/tests/stress.rs +++ b/tests/stress.rs @@ -104,7 +104,7 @@ struct TestContext { async fn create_test_context() -> TestContext { let dir = tempdir().unwrap(); let dbfile = dir.path().join("db.sqlite"); - let ctx = Context::new(Box::new(cb), "FakeOs".into(), dbfile) + let ctx = Context::new(Box::new(cb), "FakeOs".into(), dbfile.into()) .await .unwrap(); TestContext { ctx, dir } From efc17983c3b24d60b2faba72c61cfe59f06f8c81 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Tue, 17 Mar 2020 10:07:52 +0100 Subject: [PATCH 011/118] switch to queue based logging --- Cargo.lock | 1 + Cargo.toml | 1 + examples/simple.rs | 2 +- src/chat.rs | 16 +++++------ src/contact.rs | 2 +- src/context.rs | 68 ++++++++++++++++------------------------------ src/error.rs | 9 ++++++ src/imex.rs | 2 +- src/test_utils.rs | 24 +++------------- tests/stress.rs | 6 +--- 10 files changed, 51 insertions(+), 80 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9e969ed74..86a3774a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -628,6 +628,7 @@ dependencies = [ "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "charset 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "debug_stub_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "deltachat_derive 2.0.0", "email 0.0.21 (git+https://github.com/deltachat/rust-email)", diff --git a/Cargo.toml b/Cargo.toml index fbdd15f96..142f3ecd0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,6 +61,7 @@ pretty_env_logger = "0.3.1" rustyline = { version = "4.1.0", optional = true } futures = "0.3.4" +crossbeam-queue = "0.2.1" [dev-dependencies] tempfile = "3.0" diff --git a/examples/simple.rs b/examples/simple.rs index c4a5aa827..c4d96f50f 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -36,7 +36,7 @@ async fn main() { let dir = tempdir().unwrap(); let dbfile = dir.path().join("db.sqlite"); println!("creating database {:?}", dbfile); - let ctx = Context::new(Box::new(cb), "FakeOs".into(), dbfile.into()) + let ctx = Context::new("FakeOs".into(), dbfile.into()) .await .expect("Failed to create context"); let running = Arc::new(RwLock::new(true)); diff --git a/src/chat.rs b/src/chat.rs index 97dbf83e3..531cda89c 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -2766,7 +2766,7 @@ mod tests { #[async_std::test] async fn test_add_contact_to_chat_ex_add_self() { // Adding self to a contact should succeed, even though it's pointless. - let t = test_context(Some(Box::new(logging_cb))).await; + let t = test_context().await; let chat_id = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo") .await .unwrap(); @@ -2814,7 +2814,7 @@ mod tests { #[async_std::test] async fn test_add_device_msg_unlabelled() { - let t = test_context(Some(Box::new(logging_cb))).await; + let t = test_context().await; // add two device-messages let mut msg1 = Message::new(Viewtype::Text); @@ -2849,7 +2849,7 @@ mod tests { #[async_std::test] async fn test_add_device_msg_labelled() { - let t = test_context(Some(Box::new(logging_cb))).await; + let t = test_context().await; // add two device-messages with the same label (second attempt is not added) let mut msg1 = Message::new(Viewtype::Text); @@ -2903,7 +2903,7 @@ mod tests { #[async_std::test] async fn test_add_device_msg_label_only() { - let t = test_context(Some(Box::new(logging_cb))).await; + let t = test_context().await; let res = add_device_msg(&t.ctx, Some(""), None).await; assert!(res.is_err()); let res = add_device_msg(&t.ctx, Some("some-label"), None).await; @@ -2923,7 +2923,7 @@ mod tests { #[async_std::test] async fn test_was_device_msg_ever_added() { - let t = test_context(Some(Box::new(logging_cb))).await; + let t = test_context().await; add_device_msg(&t.ctx, Some("some-label"), None).await.ok(); assert!(was_device_msg_ever_added(&t.ctx, "some-label") .await @@ -2947,7 +2947,7 @@ mod tests { #[async_std::test] async fn test_delete_device_chat() { - let t = test_context(Some(Box::new(logging_cb))).await; + let t = test_context().await; let mut msg = Message::new(Viewtype::Text); msg.text = Some("message text".to_string()); @@ -2967,7 +2967,7 @@ mod tests { #[async_std::test] async fn test_device_chat_cannot_sent() { - let t = test_context(Some(Box::new(logging_cb))).await; + let t = test_context().await; t.ctx.update_device_chats().await.unwrap(); let (device_chat_id, _) = create_or_lookup_by_contact_id(&t.ctx, DC_CONTACT_ID_DEVICE, Blocked::Not) @@ -2987,7 +2987,7 @@ mod tests { #[async_std::test] async fn test_delete_and_reset_all_device_msgs() { - let t = test_context(Some(Box::new(logging_cb))).await; + let t = test_context().await; let mut msg = Message::new(Viewtype::Text); msg.text = Some("message text".to_string()); let msg_id1 = add_device_msg(&t.ctx, Some("some-label"), Some(&mut msg)) diff --git a/src/contact.rs b/src/contact.rs index af8a5855e..cb3ab93be 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -1269,7 +1269,7 @@ mod tests { #[async_std::test] async fn test_is_self_addr() -> Result<()> { - let t = test_context(None).await; + let t = test_context().await; assert!(t.ctx.is_self_addr("me@me.org").await.is_err()); let addr = configure_alice_keypair(&t.ctx).await; diff --git a/src/context.rs b/src/context.rs index cba9d15f4..b0678fe8c 100644 --- a/src/context.rs +++ b/src/context.rs @@ -6,6 +6,7 @@ use std::sync::atomic::AtomicBool; use async_std::path::{Path, PathBuf}; use async_std::sync::{Arc, Mutex, RwLock}; +use crossbeam_queue::SegQueue; use crate::chat::*; use crate::config::Config; @@ -24,16 +25,6 @@ use crate::param::Params; use crate::smtp::Smtp; use crate::sql::Sql; -/// Callback function type for [Context] -/// -/// # Parameters -/// -/// * `context` - The context object as returned by [Context::new]. -/// * `event` - One of the [Event] items. -/// * `data1` - Depends on the event parameter, see [Event]. -/// * `data2` - Depends on the event parameter, see [Event]. -pub type ContextCallback = dyn Fn(&Context, Event) -> () + Send + Sync; - #[derive(DebugStub)] pub struct Context { /// Database file path @@ -48,8 +39,6 @@ pub struct Context { pub mvbox_thread: JobThread, pub smtp: Smtp, pub oauth2_critical: Arc>, - #[debug_stub = "Callback"] - cb: Box, pub os_name: Option, pub cmdline_sel_chat_id: Arc>, pub(crate) bob: Arc>, @@ -58,6 +47,8 @@ pub struct Context { /// Mutex to avoid generating the key for the user more than once. pub generating_key_mutex: Mutex<()>, pub translated_stockstrings: RwLock>, + + pub(crate) logs: SegQueue, } #[derive(Debug, PartialEq, Eq)] @@ -83,11 +74,7 @@ pub fn get_info() -> HashMap<&'static str, String> { impl Context { /// Creates new context. - pub async fn new( - cb: Box, - os_name: String, - dbfile: PathBuf, - ) -> Result { + pub async fn new(os_name: String, dbfile: PathBuf) -> Result { pretty_env_logger::try_init_timed().ok(); let mut blob_fname = OsString::new(); @@ -95,13 +82,12 @@ impl Context { blob_fname.push("-blobs"); let blobdir = dbfile.with_file_name(blob_fname); if !blobdir.exists().await { - std::fs::create_dir_all(&blobdir)?; + async_std::fs::create_dir_all(&blobdir).await?; } - Context::with_blobdir(cb, os_name, dbfile, blobdir).await + Context::with_blobdir(os_name, dbfile, blobdir).await } pub async fn with_blobdir( - cb: Box, os_name: String, dbfile: PathBuf, blobdir: PathBuf, @@ -114,7 +100,6 @@ impl Context { let ctx = Context { blobdir, dbfile, - cb, os_name: Some(os_name), running_state: Arc::new(RwLock::new(Default::default())), sql: Sql::new(), @@ -130,6 +115,7 @@ impl Context { perform_inbox_jobs_needed: Default::default(), generating_key_mutex: Mutex::new(()), translated_stockstrings: RwLock::new(HashMap::new()), + logs: SegQueue::new(), }; ensure!( @@ -151,7 +137,17 @@ impl Context { } pub fn call_cb(&self, event: Event) { - (*self.cb)(self, event); + self.logs.push(event); + } + + pub fn get_next_event(&self) -> Result { + let event = self.logs.pop()?; + + Ok(event) + } + + pub fn has_next_event(&self) -> bool { + !self.logs.is_empty() } /******************************************************************************* @@ -518,7 +514,7 @@ mod tests { let tmp = tempfile::tempdir().unwrap(); let dbfile = tmp.path().join("db.sqlite"); std::fs::write(&dbfile, b"123").unwrap(); - let res = Context::new(Box::new(|_, _| ()), "FakeOs".into(), dbfile.into()).await; + let res = Context::new("FakeOs".into(), dbfile.into()).await; assert!(res.is_err()); } @@ -533,9 +529,7 @@ mod tests { async fn test_blobdir_exists() { let tmp = tempfile::tempdir().unwrap(); let dbfile = tmp.path().join("db.sqlite"); - Context::new(Box::new(|_, _| ()), "FakeOS".into(), dbfile.into()) - .await - .unwrap(); + Context::new("FakeOS".into(), dbfile.into()).await.unwrap(); let blobdir = tmp.path().join("db.sqlite-blobs"); assert!(blobdir.is_dir()); } @@ -546,7 +540,7 @@ mod tests { let dbfile = tmp.path().join("db.sqlite"); let blobdir = tmp.path().join("db.sqlite-blobs"); std::fs::write(&blobdir, b"123").unwrap(); - let res = Context::new(Box::new(|_, _| ()), "FakeOS".into(), dbfile.into()).await; + let res = Context::new("FakeOS".into(), dbfile.into()).await; assert!(res.is_err()); } @@ -556,9 +550,7 @@ mod tests { let subdir = tmp.path().join("subdir"); let dbfile = subdir.join("db.sqlite"); let dbfile2 = dbfile.clone(); - Context::new(Box::new(|_, _| ()), "FakeOS".into(), dbfile.into()) - .await - .unwrap(); + Context::new("FakeOS".into(), dbfile.into()).await.unwrap(); assert!(subdir.is_dir()); assert!(dbfile2.is_file()); } @@ -568,13 +560,7 @@ mod tests { let tmp = tempfile::tempdir().unwrap(); let dbfile = tmp.path().join("db.sqlite"); let blobdir = PathBuf::new(); - let res = Context::with_blobdir( - Box::new(|_, _| ()), - "FakeOS".into(), - dbfile.into(), - blobdir.into(), - ) - .await; + let res = Context::with_blobdir("FakeOS".into(), dbfile.into(), blobdir.into()).await; assert!(res.is_err()); } @@ -583,13 +569,7 @@ mod tests { let tmp = tempfile::tempdir().unwrap(); let dbfile = tmp.path().join("db.sqlite"); let blobdir = tmp.path().join("blobs"); - let res = Context::with_blobdir( - Box::new(|_, _| ()), - "FakeOS".into(), - dbfile.into(), - blobdir.into(), - ) - .await; + let res = Context::with_blobdir("FakeOS".into(), dbfile.into(), blobdir.into()).await; assert!(res.is_err()); } diff --git a/src/error.rs b/src/error.rs index 9381fd0f4..49f87a706 100644 --- a/src/error.rs +++ b/src/error.rs @@ -57,6 +57,9 @@ pub enum Error { #[fail(display = "Not Configured")] NotConfigured, + + #[fail(display = "No event available")] + PopError(crossbeam_queue::PopError), } pub type Result = std::result::Result; @@ -67,6 +70,12 @@ impl From for Error { } } +impl From for Error { + fn from(err: crossbeam_queue::PopError) -> Error { + Error::PopError(err) + } +} + impl From for Error { fn from(err: base64::DecodeError) -> Error { Error::Base64Decode(err) diff --git a/src/imex.rs b/src/imex.rs index c5f82b97e..ae5589321 100644 --- a/src/imex.rs +++ b/src/imex.rs @@ -776,7 +776,7 @@ mod tests { #[async_std::test] async fn test_render_setup_file() { - let t = test_context(Some(Box::new(logging_cb))).await; + let t = test_context().await; configure_alice_keypair(&t.ctx).await; let msg = render_setup_file(&t.ctx, "hello").await.unwrap(); diff --git a/src/test_utils.rs b/src/test_utils.rs index 86c61adf2..394fafc02 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -5,9 +5,8 @@ use tempfile::{tempdir, TempDir}; use crate::config::Config; -use crate::context::{Context, ContextCallback}; +use crate::context::Context; use crate::dc_tools::EmailAddress; -use crate::events::Event; use crate::key::{self, DcKey}; /// A Context and temporary directory. @@ -25,16 +24,10 @@ pub(crate) struct TestContext { /// "db.sqlite" in the [TestContext.dir] directory. /// /// [Context]: crate::context::Context -pub(crate) async fn test_context(callback: Option>) -> TestContext { +pub(crate) async fn test_context() -> TestContext { let dir = tempdir().unwrap(); let dbfile = dir.path().join("db.sqlite"); - let cb: Box = match callback { - Some(cb) => cb, - None => Box::new(|_, _| ()), - }; - let ctx = Context::new(cb, "FakeOs".into(), dbfile.into()) - .await - .unwrap(); + let ctx = Context::new("FakeOs".into(), dbfile.into()).await.unwrap(); TestContext { ctx, dir } } @@ -44,16 +37,7 @@ pub(crate) async fn test_context(callback: Option>) -> Test /// specified in [test_context] but there is no callback hooked up, /// i.e. [Context::call_cb] will always return `0`. pub(crate) async fn dummy_context() -> TestContext { - test_context(None).await -} - -pub(crate) fn logging_cb(_ctx: &Context, evt: Event) { - match evt { - Event::Info(msg) => println!("I: {}", msg), - Event::Warning(msg) => println!("W: {}", msg), - Event::Error(msg) => println!("E: {}", msg), - _ => (), - } + test_context().await } /// Load a pre-generated keypair for alice@example.com from disk. diff --git a/tests/stress.rs b/tests/stress.rs index f44aa1e19..ed6710fc8 100644 --- a/tests/stress.rs +++ b/tests/stress.rs @@ -93,8 +93,6 @@ async fn stress_functions(context: &Context) { // free(qr.cast()); } -fn cb(_context: &Context, _event: Event) {} - #[allow(dead_code)] struct TestContext { ctx: Context, @@ -104,9 +102,7 @@ struct TestContext { async fn create_test_context() -> TestContext { let dir = tempdir().unwrap(); let dbfile = dir.path().join("db.sqlite"); - let ctx = Context::new(Box::new(cb), "FakeOs".into(), dbfile.into()) - .await - .unwrap(); + let ctx = Context::new("FakeOs".into(), dbfile.into()).await.unwrap(); TestContext { ctx, dir } } From ce5b95f8e541b663666cd321e43d3b68421adde5 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Tue, 17 Mar 2020 13:48:03 +0100 Subject: [PATCH 012/118] start setting up new scheduler --- src/configure/mod.rs | 843 +++++++++++++++---------------- src/context.rs | 100 ++-- src/imap/idle.rs | 46 +- src/imap/mod.rs | 202 ++++---- src/imap/select_folder.rs | 30 +- src/job.rs | 1005 +++++++++++++++++++------------------ src/job_thread.rs | 14 +- src/lib.rs | 1 + src/oauth2.rs | 5 +- src/scheduler.rs | 237 +++++++++ src/smtp/mod.rs | 53 +- src/smtp/send.rs | 11 +- 12 files changed, 1400 insertions(+), 1147 deletions(-) create mode 100644 src/scheduler.rs diff --git a/src/configure/mod.rs b/src/configure/mod.rs index b73aff7f5..726883e66 100644 --- a/src/configure/mod.rs +++ b/src/configure/mod.rs @@ -52,402 +52,403 @@ impl Context { ******************************************************************************/ #[allow(clippy::cognitive_complexity)] pub(crate) async fn job_configure_imap(context: &Context) -> job::Status { - if !context.sql.is_open().await { - error!(context, "Cannot configure, database not opened.",); - progress!(context, 0); - return job::Status::Finished(Err(format_err!("Database not opened"))); - } - if !context.alloc_ongoing().await { - progress!(context, 0); - return job::Status::Finished(Err(format_err!("Cannot allocated ongoing process"))); - } - let mut success = false; - let mut imap_connected_here = false; - let mut smtp_connected_here = false; + unimplemented!() + // if !context.sql.is_open().await { + // error!(context, "Cannot configure, database not opened.",); + // progress!(context, 0); + // return job::Status::Finished(Err(format_err!("Database not opened"))); + // } + // if !context.alloc_ongoing().await { + // progress!(context, 0); + // return job::Status::Finished(Err(format_err!("Cannot allocated ongoing process"))); + // } + // let mut success = false; + // let mut imap_connected_here = false; + // let mut smtp_connected_here = false; - let mut param_autoconfig: Option = None; + // let mut param_autoconfig: Option = None; - context.inbox_thread.imap.disconnect(context).await; - context.sentbox_thread.imap.disconnect(context).await; - context.mvbox_thread.imap.disconnect(context).await; - context.smtp.disconnect().await; + // // context.inbox_thread.imap.disconnect(context).await; + // // context.sentbox_thread.imap.disconnect(context).await; + // // context.mvbox_thread.imap.disconnect(context).await; + // // context.smtp.disconnect().await; - info!(context, "Configure ...",); + // info!(context, "Configure ...",); - // Variables that are shared between steps: - let mut param = LoginParam::from_database(context, "").await; - // need all vars here to be mutable because rust thinks the same step could be called multiple times - // and also initialize, because otherwise rust thinks it's used while unitilized, even if thats not the case as the loop goes only forward - let mut param_domain = "undefined.undefined".to_owned(); - let mut param_addr_urlencoded: String = - "Internal Error: this value should never be used".to_owned(); - let mut keep_flags = 0; + // // Variables that are shared between steps: + // let mut param = LoginParam::from_database(context, "").await; + // // need all vars here to be mutable because rust thinks the same step could be called multiple times + // // and also initialize, because otherwise rust thinks it's used while unitilized, even if thats not the case as the loop goes only forward + // let mut param_domain = "undefined.undefined".to_owned(); + // let mut param_addr_urlencoded: String = + // "Internal Error: this value should never be used".to_owned(); + // let mut keep_flags = 0; - const STEP_12_USE_AUTOCONFIG: u8 = 12; - const STEP_13_AFTER_AUTOCONFIG: u8 = 13; + // const STEP_12_USE_AUTOCONFIG: u8 = 12; + // const STEP_13_AFTER_AUTOCONFIG: u8 = 13; - let mut step_counter: u8 = 0; - while !context.shall_stop_ongoing().await { - step_counter += 1; + // let mut step_counter: u8 = 0; + // while !context.shall_stop_ongoing().await { + // step_counter += 1; - let success = match step_counter { - // Read login parameters from the database - 1 => { - progress!(context, 1); - if param.addr.is_empty() { - error!(context, "Please enter an email address.",); - } - !param.addr.is_empty() - } - // Step 1: Load the parameters and check email-address and password - 2 => { - if 0 != param.server_flags & DC_LP_AUTH_OAUTH2 { - // the used oauth2 addr may differ, check this. - // if dc_get_oauth2_addr() is not available in the oauth2 implementation, - // just use the given one. - progress!(context, 10); - if let Some(oauth2_addr) = - dc_get_oauth2_addr(context, ¶m.addr, ¶m.mail_pw) - .await - .and_then(|e| e.parse().ok()) - { - info!(context, "Authorized address is {}", oauth2_addr); - param.addr = oauth2_addr; - context - .sql - .set_raw_config(context, "addr", Some(param.addr.as_str())) - .await - .ok(); - } - progress!(context, 20); - } - true // no oauth? - just continue it's no error - } - 3 => { - if let Ok(parsed) = param.addr.parse() { - let parsed: EmailAddress = parsed; - param_domain = parsed.domain; - param_addr_urlencoded = - utf8_percent_encode(¶m.addr, NON_ALPHANUMERIC).to_string(); - true - } else { - error!(context, "Bad email-address."); - false - } - } - // Step 2: Autoconfig - 4 => { - progress!(context, 200); + // let success = match step_counter { + // // Read login parameters from the database + // 1 => { + // progress!(context, 1); + // if param.addr.is_empty() { + // error!(context, "Please enter an email address.",); + // } + // !param.addr.is_empty() + // } + // // Step 1: Load the parameters and check email-address and password + // 2 => { + // if 0 != param.server_flags & DC_LP_AUTH_OAUTH2 { + // // the used oauth2 addr may differ, check this. + // // if dc_get_oauth2_addr() is not available in the oauth2 implementation, + // // just use the given one. + // progress!(context, 10); + // if let Some(oauth2_addr) = + // dc_get_oauth2_addr(context, ¶m.addr, ¶m.mail_pw) + // .await + // .and_then(|e| e.parse().ok()) + // { + // info!(context, "Authorized address is {}", oauth2_addr); + // param.addr = oauth2_addr; + // context + // .sql + // .set_raw_config(context, "addr", Some(param.addr.as_str())) + // .await + // .ok(); + // } + // progress!(context, 20); + // } + // true // no oauth? - just continue it's no error + // } + // 3 => { + // if let Ok(parsed) = param.addr.parse() { + // let parsed: EmailAddress = parsed; + // param_domain = parsed.domain; + // param_addr_urlencoded = + // utf8_percent_encode(¶m.addr, NON_ALPHANUMERIC).to_string(); + // true + // } else { + // error!(context, "Bad email-address."); + // false + // } + // } + // // Step 2: Autoconfig + // 4 => { + // progress!(context, 200); - if param.mail_server.is_empty() - && param.mail_port == 0 - /*&¶m.mail_user.is_empty() -- the user can enter a loginname which is used by autoconfig then */ - && param.send_server.is_empty() - && param.send_port == 0 - && param.send_user.is_empty() - /*&¶m.send_pw.is_empty() -- the password cannot be auto-configured and is no criterion for autoconfig or not */ - && (param.server_flags & !DC_LP_AUTH_OAUTH2) == 0 - { - // no advanced parameters entered by the user: query provider-database or do Autoconfig - keep_flags = param.server_flags & DC_LP_AUTH_OAUTH2; - if let Some(new_param) = get_offline_autoconfig(context, ¶m) { - // got parameters from our provider-database, skip Autoconfig, preserve the OAuth2 setting - param_autoconfig = Some(new_param); - step_counter = STEP_12_USE_AUTOCONFIG - 1; // minus one as step_counter is increased on next loop - } - } else { - // advanced parameters entered by the user: skip Autoconfig - step_counter = STEP_13_AFTER_AUTOCONFIG - 1; // minus one as step_counter is increased on next loop - } - true - } - /* A. Search configurations from the domain used in the email-address, prefer encrypted */ - 5 => { - if param_autoconfig.is_none() { - let url = format!( - "https://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}", - param_domain, param_addr_urlencoded - ); - param_autoconfig = moz_autoconfigure(context, &url, ¶m).ok(); - } - true - } - 6 => { - progress!(context, 300); - if param_autoconfig.is_none() { - // the doc does not mention `emailaddress=`, however, Thunderbird adds it, see https://releases.mozilla.org/pub/thunderbird/ , which makes some sense - let url = format!( - "https://{}/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress={}", - param_domain, param_addr_urlencoded - ); - param_autoconfig = moz_autoconfigure(context, &url, ¶m).ok(); - } - true - } - /* Outlook section start ------------- */ - /* Outlook uses always SSL but different domains (this comment describes the next two steps) */ - 7 => { - progress!(context, 310); - if param_autoconfig.is_none() { - let url = format!("https://{}/autodiscover/autodiscover.xml", param_domain); - param_autoconfig = outlk_autodiscover(context, &url, ¶m).ok(); - } - true - } - 8 => { - progress!(context, 320); - if param_autoconfig.is_none() { - let url = format!( - "https://{}{}/autodiscover/autodiscover.xml", - "autodiscover.", param_domain - ); - param_autoconfig = outlk_autodiscover(context, &url, ¶m).ok(); - } - true - } - /* ----------- Outlook section end */ - 9 => { - progress!(context, 330); - if param_autoconfig.is_none() { - let url = format!( - "http://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}", - param_domain, param_addr_urlencoded - ); - param_autoconfig = moz_autoconfigure(context, &url, ¶m).ok(); - } - true - } - 10 => { - progress!(context, 340); - if param_autoconfig.is_none() { - // do not transfer the email-address unencrypted - let url = format!( - "http://{}/.well-known/autoconfig/mail/config-v1.1.xml", - param_domain - ); - param_autoconfig = moz_autoconfigure(context, &url, ¶m).ok(); - } - true - } - /* B. If we have no configuration yet, search configuration in Thunderbird's centeral database */ - 11 => { - progress!(context, 350); - if param_autoconfig.is_none() { - /* always SSL for Thunderbird's database */ - let url = format!("https://autoconfig.thunderbird.net/v1.1/{}", param_domain); - param_autoconfig = moz_autoconfigure(context, &url, ¶m).ok(); - } - true - } - /* C. Do we have any autoconfig result? - If you change the match-number here, also update STEP_12_COPY_AUTOCONFIG above - */ - STEP_12_USE_AUTOCONFIG => { - progress!(context, 500); - if let Some(ref cfg) = param_autoconfig { - info!(context, "Got autoconfig: {}", &cfg); - if !cfg.mail_user.is_empty() { - param.mail_user = cfg.mail_user.clone(); - } - param.mail_server = cfg.mail_server.clone(); /* all other values are always NULL when entering autoconfig */ - param.mail_port = cfg.mail_port; - param.send_server = cfg.send_server.clone(); - param.send_port = cfg.send_port; - param.send_user = cfg.send_user.clone(); - param.server_flags = cfg.server_flags; - /* although param_autoconfig's data are no longer needed from, - it is used to later to prevent trying variations of port/server/logins */ - } - param.server_flags |= keep_flags; - true - } - // Step 3: Fill missing fields with defaults - // If you change the match-number here, also update STEP_13_AFTER_AUTOCONFIG above - STEP_13_AFTER_AUTOCONFIG => { - if param.mail_server.is_empty() { - param.mail_server = format!("imap.{}", param_domain,) - } - if param.mail_port == 0 { - param.mail_port = if 0 != param.server_flags & (0x100 | 0x400) { - 143 - } else { - 993 - } - } - if param.mail_user.is_empty() { - param.mail_user = param.addr.clone(); - } - if param.send_server.is_empty() && !param.mail_server.is_empty() { - param.send_server = param.mail_server.clone(); - if param.send_server.starts_with("imap.") { - param.send_server = param.send_server.replacen("imap", "smtp", 1); - } - } - if param.send_port == 0 { - param.send_port = if 0 != param.server_flags & DC_LP_SMTP_SOCKET_STARTTLS as i32 - { - 587 - } else if 0 != param.server_flags & DC_LP_SMTP_SOCKET_PLAIN as i32 { - 25 - } else { - 465 - } - } - if param.send_user.is_empty() && !param.mail_user.is_empty() { - param.send_user = param.mail_user.clone(); - } - if param.send_pw.is_empty() && !param.mail_pw.is_empty() { - param.send_pw = param.mail_pw.clone() - } - if !dc_exactly_one_bit_set(param.server_flags & DC_LP_AUTH_FLAGS as i32) { - param.server_flags &= !(DC_LP_AUTH_FLAGS as i32); - param.server_flags |= DC_LP_AUTH_NORMAL as i32 - } - if !dc_exactly_one_bit_set(param.server_flags & DC_LP_IMAP_SOCKET_FLAGS as i32) { - param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS as i32); - param.server_flags |= if param.send_port == 143 { - DC_LP_IMAP_SOCKET_STARTTLS as i32 - } else { - DC_LP_IMAP_SOCKET_SSL as i32 - } - } - if !dc_exactly_one_bit_set(param.server_flags & (DC_LP_SMTP_SOCKET_FLAGS as i32)) { - param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32); - param.server_flags |= if param.send_port == 587 { - DC_LP_SMTP_SOCKET_STARTTLS as i32 - } else if param.send_port == 25 { - DC_LP_SMTP_SOCKET_PLAIN as i32 - } else { - DC_LP_SMTP_SOCKET_SSL as i32 - } - } - /* do we have a complete configuration? */ - if param.mail_server.is_empty() - || param.mail_port == 0 - || param.mail_user.is_empty() - || param.mail_pw.is_empty() - || param.send_server.is_empty() - || param.send_port == 0 - || param.send_user.is_empty() - || param.send_pw.is_empty() - || param.server_flags == 0 - { - error!(context, "Account settings incomplete."); - false - } else { - true - } - } - 14 => { - progress!(context, 600); - /* try to connect to IMAP - if we did not got an autoconfig, - do some further tries with different settings and username variations */ - imap_connected_here = - try_imap_connections(context, &mut param, param_autoconfig.is_some()).await; - imap_connected_here - } - 15 => { - progress!(context, 800); - smtp_connected_here = - try_smtp_connections(context, &mut param, param_autoconfig.is_some()).await; - smtp_connected_here - } - 16 => { - progress!(context, 900); - let create_mvbox = context.get_config_bool(Config::MvboxWatch).await - || context.get_config_bool(Config::MvboxMove).await; - let imap = &context.inbox_thread.imap; - if let Err(err) = imap.ensure_configured_folders(context, create_mvbox).await { - warn!(context, "configuring folders failed: {:?}", err); - false - } else { - let res = imap.select_with_uidvalidity(context, "INBOX").await; - if let Err(err) = res { - error!(context, "could not read INBOX status: {:?}", err); - false - } else { - true - } - } - } - 17 => { - progress!(context, 910); - /* configuration success - write back the configured parameters with the "configured_" prefix; also write the "configured"-flag */ - param - .save_to_database( - context, - "configured_", /*the trailing underscore is correct*/ - ) - .await - .ok(); + // if param.mail_server.is_empty() + // && param.mail_port == 0 + // /*&¶m.mail_user.is_empty() -- the user can enter a loginname which is used by autoconfig then */ + // && param.send_server.is_empty() + // && param.send_port == 0 + // && param.send_user.is_empty() + // /*&¶m.send_pw.is_empty() -- the password cannot be auto-configured and is no criterion for autoconfig or not */ + // && (param.server_flags & !DC_LP_AUTH_OAUTH2) == 0 + // { + // // no advanced parameters entered by the user: query provider-database or do Autoconfig + // keep_flags = param.server_flags & DC_LP_AUTH_OAUTH2; + // if let Some(new_param) = get_offline_autoconfig(context, ¶m) { + // // got parameters from our provider-database, skip Autoconfig, preserve the OAuth2 setting + // param_autoconfig = Some(new_param); + // step_counter = STEP_12_USE_AUTOCONFIG - 1; // minus one as step_counter is increased on next loop + // } + // } else { + // // advanced parameters entered by the user: skip Autoconfig + // step_counter = STEP_13_AFTER_AUTOCONFIG - 1; // minus one as step_counter is increased on next loop + // } + // true + // } + // /* A. Search configurations from the domain used in the email-address, prefer encrypted */ + // 5 => { + // if param_autoconfig.is_none() { + // let url = format!( + // "https://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}", + // param_domain, param_addr_urlencoded + // ); + // param_autoconfig = moz_autoconfigure(context, &url, ¶m).ok(); + // } + // true + // } + // 6 => { + // progress!(context, 300); + // if param_autoconfig.is_none() { + // // the doc does not mention `emailaddress=`, however, Thunderbird adds it, see https://releases.mozilla.org/pub/thunderbird/ , which makes some sense + // let url = format!( + // "https://{}/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress={}", + // param_domain, param_addr_urlencoded + // ); + // param_autoconfig = moz_autoconfigure(context, &url, ¶m).ok(); + // } + // true + // } + // /* Outlook section start ------------- */ + // /* Outlook uses always SSL but different domains (this comment describes the next two steps) */ + // 7 => { + // progress!(context, 310); + // if param_autoconfig.is_none() { + // let url = format!("https://{}/autodiscover/autodiscover.xml", param_domain); + // param_autoconfig = outlk_autodiscover(context, &url, ¶m).ok(); + // } + // true + // } + // 8 => { + // progress!(context, 320); + // if param_autoconfig.is_none() { + // let url = format!( + // "https://{}{}/autodiscover/autodiscover.xml", + // "autodiscover.", param_domain + // ); + // param_autoconfig = outlk_autodiscover(context, &url, ¶m).ok(); + // } + // true + // } + // /* ----------- Outlook section end */ + // 9 => { + // progress!(context, 330); + // if param_autoconfig.is_none() { + // let url = format!( + // "http://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}", + // param_domain, param_addr_urlencoded + // ); + // param_autoconfig = moz_autoconfigure(context, &url, ¶m).ok(); + // } + // true + // } + // 10 => { + // progress!(context, 340); + // if param_autoconfig.is_none() { + // // do not transfer the email-address unencrypted + // let url = format!( + // "http://{}/.well-known/autoconfig/mail/config-v1.1.xml", + // param_domain + // ); + // param_autoconfig = moz_autoconfigure(context, &url, ¶m).ok(); + // } + // true + // } + // /* B. If we have no configuration yet, search configuration in Thunderbird's centeral database */ + // 11 => { + // progress!(context, 350); + // if param_autoconfig.is_none() { + // /* always SSL for Thunderbird's database */ + // let url = format!("https://autoconfig.thunderbird.net/v1.1/{}", param_domain); + // param_autoconfig = moz_autoconfigure(context, &url, ¶m).ok(); + // } + // true + // } + // /* C. Do we have any autoconfig result? + // If you change the match-number here, also update STEP_12_COPY_AUTOCONFIG above + // */ + // STEP_12_USE_AUTOCONFIG => { + // progress!(context, 500); + // if let Some(ref cfg) = param_autoconfig { + // info!(context, "Got autoconfig: {}", &cfg); + // if !cfg.mail_user.is_empty() { + // param.mail_user = cfg.mail_user.clone(); + // } + // param.mail_server = cfg.mail_server.clone(); /* all other values are always NULL when entering autoconfig */ + // param.mail_port = cfg.mail_port; + // param.send_server = cfg.send_server.clone(); + // param.send_port = cfg.send_port; + // param.send_user = cfg.send_user.clone(); + // param.server_flags = cfg.server_flags; + // /* although param_autoconfig's data are no longer needed from, + // it is used to later to prevent trying variations of port/server/logins */ + // } + // param.server_flags |= keep_flags; + // true + // } + // // Step 3: Fill missing fields with defaults + // // If you change the match-number here, also update STEP_13_AFTER_AUTOCONFIG above + // STEP_13_AFTER_AUTOCONFIG => { + // if param.mail_server.is_empty() { + // param.mail_server = format!("imap.{}", param_domain,) + // } + // if param.mail_port == 0 { + // param.mail_port = if 0 != param.server_flags & (0x100 | 0x400) { + // 143 + // } else { + // 993 + // } + // } + // if param.mail_user.is_empty() { + // param.mail_user = param.addr.clone(); + // } + // if param.send_server.is_empty() && !param.mail_server.is_empty() { + // param.send_server = param.mail_server.clone(); + // if param.send_server.starts_with("imap.") { + // param.send_server = param.send_server.replacen("imap", "smtp", 1); + // } + // } + // if param.send_port == 0 { + // param.send_port = if 0 != param.server_flags & DC_LP_SMTP_SOCKET_STARTTLS as i32 + // { + // 587 + // } else if 0 != param.server_flags & DC_LP_SMTP_SOCKET_PLAIN as i32 { + // 25 + // } else { + // 465 + // } + // } + // if param.send_user.is_empty() && !param.mail_user.is_empty() { + // param.send_user = param.mail_user.clone(); + // } + // if param.send_pw.is_empty() && !param.mail_pw.is_empty() { + // param.send_pw = param.mail_pw.clone() + // } + // if !dc_exactly_one_bit_set(param.server_flags & DC_LP_AUTH_FLAGS as i32) { + // param.server_flags &= !(DC_LP_AUTH_FLAGS as i32); + // param.server_flags |= DC_LP_AUTH_NORMAL as i32 + // } + // if !dc_exactly_one_bit_set(param.server_flags & DC_LP_IMAP_SOCKET_FLAGS as i32) { + // param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS as i32); + // param.server_flags |= if param.send_port == 143 { + // DC_LP_IMAP_SOCKET_STARTTLS as i32 + // } else { + // DC_LP_IMAP_SOCKET_SSL as i32 + // } + // } + // if !dc_exactly_one_bit_set(param.server_flags & (DC_LP_SMTP_SOCKET_FLAGS as i32)) { + // param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32); + // param.server_flags |= if param.send_port == 587 { + // DC_LP_SMTP_SOCKET_STARTTLS as i32 + // } else if param.send_port == 25 { + // DC_LP_SMTP_SOCKET_PLAIN as i32 + // } else { + // DC_LP_SMTP_SOCKET_SSL as i32 + // } + // } + // /* do we have a complete configuration? */ + // if param.mail_server.is_empty() + // || param.mail_port == 0 + // || param.mail_user.is_empty() + // || param.mail_pw.is_empty() + // || param.send_server.is_empty() + // || param.send_port == 0 + // || param.send_user.is_empty() + // || param.send_pw.is_empty() + // || param.server_flags == 0 + // { + // error!(context, "Account settings incomplete."); + // false + // } else { + // true + // } + // } + // 14 => { + // progress!(context, 600); + // /* try to connect to IMAP - if we did not got an autoconfig, + // do some further tries with different settings and username variations */ + // imap_connected_here = + // try_imap_connections(context, &mut param, param_autoconfig.is_some()).await; + // imap_connected_here + // } + // 15 => { + // progress!(context, 800); + // smtp_connected_here = + // try_smtp_connections(context, &mut param, param_autoconfig.is_some()).await; + // smtp_connected_here + // } + // 16 => { + // progress!(context, 900); + // let create_mvbox = context.get_config_bool(Config::MvboxWatch).await + // || context.get_config_bool(Config::MvboxMove).await; + // let imap = &context.inbox_thread.imap; + // if let Err(err) = imap.ensure_configured_folders(context, create_mvbox).await { + // warn!(context, "configuring folders failed: {:?}", err); + // false + // } else { + // let res = imap.select_with_uidvalidity(context, "INBOX").await; + // if let Err(err) = res { + // error!(context, "could not read INBOX status: {:?}", err); + // false + // } else { + // true + // } + // } + // } + // 17 => { + // progress!(context, 910); + // /* configuration success - write back the configured parameters with the "configured_" prefix; also write the "configured"-flag */ + // param + // .save_to_database( + // context, + // "configured_", /*the trailing underscore is correct*/ + // ) + // .await + // .ok(); - context - .sql - .set_raw_config_bool(context, "configured", true) - .await - .ok(); - true - } - 18 => { - progress!(context, 920); - // we generate the keypair just now - we could also postpone this until the first message is sent, however, - // this may result in a unexpected and annoying delay when the user sends his very first message - // (~30 seconds on a Moto G4 play) and might looks as if message sending is always that slow. - success = e2ee::ensure_secret_key_exists(context).await.is_ok(); - info!(context, "key generation completed"); - progress!(context, 940); - break; // We are done here - } - _ => { - error!(context, "Internal error: step counter out of bound",); - break; - } - }; + // context + // .sql + // .set_raw_config_bool(context, "configured", true) + // .await + // .ok(); + // true + // } + // 18 => { + // progress!(context, 920); + // // we generate the keypair just now - we could also postpone this until the first message is sent, however, + // // this may result in a unexpected and annoying delay when the user sends his very first message + // // (~30 seconds on a Moto G4 play) and might looks as if message sending is always that slow. + // success = e2ee::ensure_secret_key_exists(context).await.is_ok(); + // info!(context, "key generation completed"); + // progress!(context, 940); + // break; // We are done here + // } + // _ => { + // error!(context, "Internal error: step counter out of bound",); + // break; + // } + // }; - if !success { - break; - } - } - if imap_connected_here { - context.inbox_thread.imap.disconnect(context).await; - } - if smtp_connected_here { - context.smtp.disconnect().await; - } + // if !success { + // break; + // } + // } + // if imap_connected_here { + // context.inbox_thread.imap.disconnect(context).await; + // } + // if smtp_connected_here { + // context.smtp.disconnect().await; + // } - // remember the entered parameters on success - // and restore to last-entered on failure. - // this way, the parameters visible to the ui are always in-sync with the current configuration. - if success { - LoginParam::from_database(context, "") - .await - .save_to_database(context, "configured_raw_") - .await - .ok(); - } else { - LoginParam::from_database(context, "configured_raw_") - .await - .save_to_database(context, "") - .await - .ok(); - } + // // remember the entered parameters on success + // // and restore to last-entered on failure. + // // this way, the parameters visible to the ui are always in-sync with the current configuration. + // if success { + // LoginParam::from_database(context, "") + // .await + // .save_to_database(context, "configured_raw_") + // .await + // .ok(); + // } else { + // LoginParam::from_database(context, "configured_raw_") + // .await + // .save_to_database(context, "") + // .await + // .ok(); + // } - if let Some(provider) = provider::get_provider_info(¶m.addr) { - if !provider.after_login_hint.is_empty() { - let mut msg = Message::new(Viewtype::Text); - msg.text = Some(provider.after_login_hint.to_string()); - if chat::add_device_msg(context, Some("core-provider-info"), Some(&mut msg)) - .await - .is_err() - { - warn!(context, "cannot add after_login_hint as core-provider-info"); - } - } - } + // if let Some(provider) = provider::get_provider_info(¶m.addr) { + // if !provider.after_login_hint.is_empty() { + // let mut msg = Message::new(Viewtype::Text); + // msg.text = Some(provider.after_login_hint.to_string()); + // if chat::add_device_msg(context, Some("core-provider-info"), Some(&mut msg)) + // .await + // .is_err() + // { + // warn!(context, "cannot add after_login_hint as core-provider-info"); + // } + // } + // } - context.free_ongoing().await; - progress!(context, if success { 1000 } else { 0 }); - job::Status::Finished(Ok(())) + // context.free_ongoing().await; + // progress!(context, if success { 1000 } else { 0 }); + // job::Status::Finished(Ok(())) } #[allow(clippy::unnecessary_unwrap)] @@ -562,24 +563,25 @@ async fn try_imap_connection( } async fn try_imap_one_param(context: &Context, param: &LoginParam) -> Option { - let inf = format!( - "imap: {}@{}:{} flags=0x{:x} certificate_checks={}", - param.mail_user, - param.mail_server, - param.mail_port, - param.server_flags, - param.imap_certificate_checks - ); - info!(context, "Trying: {}", inf); - if context.inbox_thread.imap.connect(context, ¶m).await { - info!(context, "success: {}", inf); - return Some(true); - } - if context.shall_stop_ongoing().await { - return Some(false); - } - info!(context, "Could not connect: {}", inf); - None + unimplemented!(); + // let inf = format!( + // "imap: {}@{}:{} flags=0x{:x} certificate_checks={}", + // param.mail_user, + // param.mail_server, + // param.mail_port, + // param.server_flags, + // param.imap_certificate_checks + // ); + // info!(context, "Trying: {}", inf); + // if context.inbox_thread.imap.connect(context, ¶m).await { + // info!(context, "success: {}", inf); + // return Some(true); + // } + // if context.shall_stop_ongoing().await { + // return Some(false); + // } + // info!(context, "Could not connect: {}", inf); + // None } async fn try_smtp_connections( @@ -613,25 +615,26 @@ async fn try_smtp_connections( } async fn try_smtp_one_param(context: &Context, param: &LoginParam) -> Option { - let inf = format!( - "smtp: {}@{}:{} flags: 0x{:x}", - param.send_user, param.send_server, param.send_port, param.server_flags - ); - info!(context, "Trying: {}", inf); - match context.smtp.connect(context, ¶m).await { - Ok(()) => { - info!(context, "success: {}", inf); - Some(true) - } - Err(err) => { - if context.shall_stop_ongoing().await { - Some(false) - } else { - warn!(context, "could not connect: {}", err); - None - } - } - } + unimplemented!() + // let inf = format!( + // "smtp: {}@{}:{} flags: 0x{:x}", + // param.send_user, param.send_server, param.send_port, param.server_flags + // ); + // info!(context, "Trying: {}", inf); + // match context.smtp.connect(context, ¶m).await { + // Ok(()) => { + // info!(context, "success: {}", inf); + // Some(true) + // } + // Err(err) => { + // if context.shall_stop_ongoing().await { + // Some(false) + // } else { + // warn!(context, "could not connect: {}", err); + // None + // } + // } + // } } #[cfg(test)] diff --git a/src/context.rs b/src/context.rs index b0678fe8c..ef3a77230 100644 --- a/src/context.rs +++ b/src/context.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::ffi::OsString; -use std::sync::atomic::AtomicBool; +use std::ops::Deref; use async_std::path::{Path, PathBuf}; use async_std::sync::{Arc, Mutex, RwLock}; @@ -22,33 +22,40 @@ use crate::login_param::LoginParam; use crate::lot::Lot; use crate::message::{self, Message, MessengerMessage, MsgId}; use crate::param::Params; +use crate::scheduler::Scheduler; use crate::smtp::Smtp; use crate::sql::Sql; -#[derive(DebugStub)] +#[derive(Debug)] pub struct Context { - /// Database file path - dbfile: PathBuf, - /// Blob directory path - blobdir: PathBuf, - pub sql: Sql, - pub perform_inbox_jobs_needed: AtomicBool, - pub probe_imap_network: AtomicBool, - pub inbox_thread: JobThread, - pub sentbox_thread: JobThread, - pub mvbox_thread: JobThread, - pub smtp: Smtp, - pub oauth2_critical: Arc>, - pub os_name: Option, - pub cmdline_sel_chat_id: Arc>, - pub(crate) bob: Arc>, - pub last_smeared_timestamp: RwLock, - pub running_state: Arc>, - /// Mutex to avoid generating the key for the user more than once. - pub generating_key_mutex: Mutex<()>, - pub translated_stockstrings: RwLock>, + pub(crate) inner: Arc, +} +impl Deref for Context { + type Target = InnerContext; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +#[derive(Debug)] +pub struct InnerContext { + /// Database file path + pub(crate) dbfile: PathBuf, + /// Blob directory path + pub(crate) blobdir: PathBuf, + pub(crate) sql: Sql, + pub(crate) os_name: Option, + pub(crate) bob: RwLock, + pub(crate) last_smeared_timestamp: RwLock, + pub(crate) running_state: RwLock, + /// Mutex to avoid generating the key for the user more than once. + pub(crate) generating_key_mutex: Mutex<()>, + pub(crate) translated_stockstrings: RwLock>, pub(crate) logs: SegQueue, + + pub(crate) scheduler: RwLock, } #[derive(Debug, PartialEq, Eq)] @@ -97,27 +104,24 @@ impl Context { "Blobdir does not exist: {}", blobdir.display() ); - let ctx = Context { + + let inner = InnerContext { blobdir, dbfile, os_name: Some(os_name), - running_state: Arc::new(RwLock::new(Default::default())), + running_state: RwLock::new(Default::default()), sql: Sql::new(), - smtp: Smtp::new(), - oauth2_critical: Arc::new(Mutex::new(())), - bob: Arc::new(RwLock::new(Default::default())), + bob: RwLock::new(Default::default()), last_smeared_timestamp: RwLock::new(0), - cmdline_sel_chat_id: Arc::new(RwLock::new(ChatId::new(0))), - inbox_thread: JobThread::new("INBOX", "configured_inbox_folder", Imap::new()), - sentbox_thread: JobThread::new("SENTBOX", "configured_sentbox_folder", Imap::new()), - mvbox_thread: JobThread::new("MVBOX", "configured_mvbox_folder", Imap::new()), - probe_imap_network: Default::default(), - perform_inbox_jobs_needed: Default::default(), generating_key_mutex: Mutex::new(()), translated_stockstrings: RwLock::new(HashMap::new()), logs: SegQueue::new(), + scheduler: RwLock::new(Scheduler::Stopped), }; + let ctx = Context { + inner: Arc::new(inner), + }; ensure!( ctx.sql.open(&ctx, &ctx.dbfile, false).await, "Failed opening sqlite database" @@ -126,6 +130,16 @@ impl Context { Ok(ctx) } + pub async fn run(&self) { + self.inner.scheduler.write().await.run().await + } + + pub async fn stop(&self) { + if self.inner.scheduler.read().await.is_running() { + self.inner.scheduler.write().await.stop().await; + } + } + /// Returns database file path. pub fn get_dbfile(&self) -> &Path { self.dbfile.as_path() @@ -160,7 +174,7 @@ impl Context { false } else { - let s_a = self.running_state.clone(); + let s_a = &self.running_state; let mut s = s_a.write().await; s.ongoing_running = true; @@ -171,7 +185,7 @@ impl Context { } pub async fn free_ongoing(&self) { - let s_a = self.running_state.clone(); + let s_a = &self.running_state; let mut s = s_a.write().await; s.ongoing_running = false; @@ -179,7 +193,7 @@ impl Context { } pub async fn has_ongoing(&self) -> bool { - let s_a = self.running_state.clone(); + let s_a = &self.running_state; let s = s_a.read().await; s.ongoing_running || !s.shall_stop_ongoing @@ -187,7 +201,7 @@ impl Context { /// Signal an ongoing process to stop. pub async fn stop_ongoing(&self) { - let s_a = self.running_state.clone(); + let s_a = &self.running_state; let mut s = s_a.write().await; if s.ongoing_running && !s.shall_stop_ongoing { @@ -199,7 +213,7 @@ impl Context { } pub async fn shall_stop_ongoing(&self) -> bool { - self.running_state.clone().read().await.shall_stop_ongoing + self.running_state.read().await.shall_stop_ongoing } /******************************************************************************* @@ -456,15 +470,7 @@ impl Context { impl Drop for Context { fn drop(&mut self) { async_std::task::block_on(async move { - info!(self, "disconnecting inbox-thread"); - self.inbox_thread.imap.disconnect(self).await; - info!(self, "disconnecting sentbox-thread"); - self.sentbox_thread.imap.disconnect(self).await; - info!(self, "disconnecting mvbox-thread"); - self.mvbox_thread.imap.disconnect(self).await; - info!(self, "disconnecting SMTP"); - self.smtp.disconnect().await; - + self.stop().await; self.sql.close(self).await; }); } diff --git a/src/imap/idle.rs b/src/imap/idle.rs index e8e213104..1959bc8f8 100644 --- a/src/imap/idle.rs +++ b/src/imap/idle.rs @@ -60,12 +60,12 @@ impl Session { } impl Imap { - pub async fn can_idle(&self) -> bool { - self.config.read().await.can_idle + pub fn can_idle(&self) -> bool { + self.config.can_idle } - pub async fn idle(&self, context: &Context, watch_folder: Option) -> Result<()> { - if !self.can_idle().await { + pub async fn idle(&mut self, context: &Context, watch_folder: Option) -> Result<()> { + if !self.can_idle() { return Err(Error::IdleAbilityMissing); } @@ -75,7 +75,7 @@ impl Imap { self.select_folder(context, watch_folder.clone()).await?; - let session = self.session.lock().await.take(); + let session = self.session.take(); let timeout = Duration::from_secs(23 * 60); if let Some(session) = session { match session.idle() { @@ -87,12 +87,12 @@ impl Imap { } let (idle_wait, interrupt) = handle.wait_with_timeout(timeout); - *self.interrupt.lock().await = Some(interrupt); + self.interrupt = Some(interrupt); - if self.skip_next_idle_wait.load(Ordering::SeqCst) { + if self.skip_next_idle_wait { // interrupt_idle has happened before we // provided self.interrupt - self.skip_next_idle_wait.store(false, Ordering::SeqCst); + self.skip_next_idle_wait = false; std::mem::drop(idle_wait); info!(context, "Idle wait was skipped"); } else { @@ -126,7 +126,7 @@ impl Imap { match res { Ok(session) => { - *self.session.lock().await = Some(Session::Secure(session)); + self.session = Some(Session::Secure(session)); } Err(err) => { // if we cannot terminate IDLE it probably @@ -143,12 +143,12 @@ impl Imap { } let (idle_wait, interrupt) = handle.wait_with_timeout(timeout); - *self.interrupt.lock().await = Some(interrupt); + self.interrupt = Some(interrupt); - if self.skip_next_idle_wait.load(Ordering::SeqCst) { + if self.skip_next_idle_wait { // interrupt_idle has happened before we // provided self.interrupt - self.skip_next_idle_wait.store(false, Ordering::SeqCst); + self.skip_next_idle_wait = false; std::mem::drop(idle_wait); info!(context, "Idle wait was skipped"); } else { @@ -182,7 +182,7 @@ impl Imap { match res { Ok(session) => { - *self.session.lock().await = Some(Session::Insecure(session)); + self.session = Some(Session::Insecure(session)); } Err(err) => { // if we cannot terminate IDLE it probably @@ -199,7 +199,7 @@ impl Imap { Ok(()) } - pub(crate) async fn fake_idle(&self, context: &Context, watch_folder: Option) { + pub(crate) async fn fake_idle(&mut self, context: &Context, watch_folder: Option) { // Idle using polling. This is also needed if we're not yet configured - // in this case, we're waiting for a configure job (and an interrupt). @@ -213,11 +213,11 @@ impl Imap { // TODO: grow sleep durations / make them more flexible let interval = async_std::stream::interval(Duration::from_secs(60)); let mut interrupt_interval = interrupt.stop_token().stop_stream(interval); - *self.interrupt.lock().await = Some(interrupt); - if self.skip_next_idle_wait.load(Ordering::SeqCst) { + self.interrupt = Some(interrupt); + if self.skip_next_idle_wait { // interrupt_idle has happened before we // provided self.interrupt - self.skip_next_idle_wait.store(false, Ordering::SeqCst); + self.skip_next_idle_wait = false; info!(context, "fake-idle wait was skipped"); } else { // loop until we are interrupted or if we fetched something @@ -229,7 +229,7 @@ impl Imap { warn!(context, "fake_idle: could not connect: {}", err); continue; } - if self.config.read().await.can_idle { + if self.config.can_idle { // we only fake-idled because network was gone during IDLE, probably break; } @@ -255,7 +255,7 @@ impl Imap { } } } - self.interrupt.lock().await.take(); + self.interrupt.take(); info!( context, @@ -268,14 +268,14 @@ impl Imap { ); } - pub async fn interrupt_idle(&self, context: &Context) { - let mut interrupt: Option = self.interrupt.lock().await.take(); + pub async fn interrupt_idle(&mut self, context: &Context) { + let mut interrupt: Option = self.interrupt.take(); if interrupt.is_none() { // idle wait is not running, signal it needs to skip - self.skip_next_idle_wait.store(true, Ordering::SeqCst); + self.skip_next_idle_wait = false; // meanwhile idle-wait may have produced the StopSource - interrupt = self.interrupt.lock().await.take(); + interrupt = self.interrupt.take(); } // let's manually drop the StopSource if interrupt.is_some() { diff --git a/src/imap/mod.rs b/src/imap/mod.rs index ee8154fce..a5f3fcda4 100644 --- a/src/imap/mod.rs +++ b/src/imap/mod.rs @@ -12,7 +12,7 @@ use async_imap::{ types::{Capability, Fetch, Flag, Mailbox, Name, NameAttribute}, }; use async_std::prelude::*; -use async_std::sync::{Mutex, RwLock}; +use async_std::sync::{Mutex, Receiver, RwLock}; use crate::config::*; use crate::constants::*; @@ -137,14 +137,15 @@ const JUST_UID: &str = "(UID)"; const BODY_FLAGS: &str = "(FLAGS BODY.PEEK[])"; const SELECT_ALL: &str = "1:*"; -#[derive(Debug, Default)] +#[derive(Debug)] pub struct Imap { - config: RwLock, - session: Mutex>, - connected: Mutex, - interrupt: Mutex>, - skip_next_idle_wait: AtomicBool, - should_reconnect: AtomicBool, + idle_interrupt: Receiver<()>, + config: ImapConfig, + session: Option, + connected: bool, + interrupt: Option, + skip_next_idle_wait: bool, + should_reconnect: bool, } #[derive(Debug)] @@ -212,39 +213,47 @@ impl Default for ImapConfig { } impl Imap { - pub fn new() -> Self { - Default::default() + pub fn new(idle_interrupt: Receiver<()>) -> Self { + Imap { + idle_interrupt, + config: Default::default(), + session: Default::default(), + connected: Default::default(), + interrupt: Default::default(), + skip_next_idle_wait: Default::default(), + should_reconnect: Default::default(), + } } - pub async fn is_connected(&self) -> bool { - *self.connected.lock().await + pub fn is_connected(&self) -> bool { + self.connected } pub fn should_reconnect(&self) -> bool { - self.should_reconnect.load(Ordering::Relaxed) + self.should_reconnect } - pub fn trigger_reconnect(&self) { - self.should_reconnect.store(true, Ordering::Relaxed) + pub fn trigger_reconnect(&mut self) { + self.should_reconnect = true; } - async fn setup_handle_if_needed(&self, context: &Context) -> Result<()> { - if self.config.read().await.imap_server.is_empty() { + async fn setup_handle_if_needed(&mut self, context: &Context) -> Result<()> { + if self.config.imap_server.is_empty() { return Err(Error::InTeardown); } if self.should_reconnect() { self.unsetup_handle(context).await; - self.should_reconnect.store(false, Ordering::Relaxed); - } else if self.is_connected().await { + self.should_reconnect = false; + } else if self.is_connected() { return Ok(()); } - let server_flags = self.config.read().await.server_flags as i32; + let server_flags = self.config.server_flags as i32; let connection_res: ImapResult = if (server_flags & (DC_LP_IMAP_SOCKET_STARTTLS | DC_LP_IMAP_SOCKET_PLAIN)) != 0 { - let config = self.config.read().await; + let config = &mut self.config; let imap_server: &str = config.imap_server.as_ref(); let imap_port = config.imap_port; @@ -259,7 +268,7 @@ impl Imap { Err(err) => Err(err), } } else { - let config = self.config.read().await; + let config = &self.config; let imap_server: &str = config.imap_server.as_ref(); let imap_port = config.imap_port; @@ -273,7 +282,7 @@ impl Imap { let login_res = match connection_res { Ok(client) => { - let config = self.config.read().await; + let config = &self.config; let imap_user: &str = config.imap_user.as_ref(); let imap_pw: &str = config.imap_pw.as_ref(); @@ -297,7 +306,7 @@ impl Imap { } Err(err) => { let message = { - let config = self.config.read().await; + let config = &self.config; let imap_server: &str = config.imap_server.as_ref(); let imap_port = config.imap_port; context @@ -314,15 +323,15 @@ impl Imap { } }; - self.should_reconnect.store(false, Ordering::Relaxed); + self.should_reconnect = false; match login_res { Ok(session) => { - *self.session.lock().await = Some(session); + self.session = Some(session); Ok(()) } Err((err, _)) => { - let imap_user = self.config.read().await.imap_user.to_owned(); + let imap_user = self.config.imap_user.to_owned(); let message = context .stock_string_repl_str(StockMessage::CannotLogin, &imap_user) .await; @@ -337,26 +346,23 @@ impl Imap { } } - async fn unsetup_handle(&self, context: &Context) { - info!( - context, - "IMAP unsetup_handle step 2 (acquiring session.lock)" - ); - if let Some(mut session) = self.session.lock().await.take() { + async fn unsetup_handle(&mut self, context: &Context) { + info!(context, "IMAP unsetup_handle step 2"); + if let Some(mut session) = self.session.take() { if let Err(err) = session.close().await { warn!(context, "failed to close connection: {:?}", err); } } - *self.connected.lock().await = false; + self.connected = false; info!(context, "IMAP unsetup_handle step 3 (clearing config)."); - self.config.write().await.selected_folder = None; - self.config.write().await.selected_mailbox = None; + self.config.selected_folder = None; + self.config.selected_mailbox = None; info!(context, "IMAP unsetup_handle step 4 (disconnected)"); } - async fn free_connect_params(&self) { - let mut cfg = self.config.write().await; + async fn free_connect_params(&mut self) { + let mut cfg = &mut self.config; cfg.addr = "".into(); cfg.imap_server = "".into(); @@ -369,8 +375,8 @@ impl Imap { } /// Connects to imap account using already-configured parameters. - pub async fn connect_configured(&self, context: &Context) -> Result<()> { - if self.is_connected().await && !self.should_reconnect() { + pub async fn connect_configured(&mut self, context: &Context) -> Result<()> { + if self.is_connected() && !self.should_reconnect() { return Ok(()); } if !context.sql.get_raw_config_bool(context, "configured").await { @@ -389,7 +395,7 @@ impl Imap { /// tries connecting to imap account using the specific login /// parameters - pub async fn connect(&self, context: &Context, lp: &LoginParam) -> bool { + pub async fn connect(&mut self, context: &Context, lp: &LoginParam) -> bool { if lp.mail_server.is_empty() || lp.mail_user.is_empty() || lp.mail_pw.is_empty() { return false; } @@ -402,7 +408,7 @@ impl Imap { let imap_pw = &lp.mail_pw; let server_flags = lp.server_flags as usize; - let mut config = self.config.write().await; + let mut config = &mut self.config; config.addr = addr.to_string(); config.imap_server = imap_server.to_string(); config.imap_port = imap_port; @@ -418,7 +424,7 @@ impl Imap { return false; } - let teardown = match &mut *self.session.lock().await { + let teardown = match &mut self.session { Some(ref mut session) => match session.capabilities().await { Ok(caps) => { if !context.sql.is_open().await { @@ -435,9 +441,9 @@ impl Imap { } }); - self.config.write().await.can_idle = can_idle; - self.config.write().await.can_move = can_move; - *self.connected.lock().await = true; + self.config.can_idle = can_idle; + self.config.can_move = can_move; + self.connected = true; emit_event!( context, Event::ImapConnected(format!( @@ -465,12 +471,12 @@ impl Imap { } } - pub async fn disconnect(&self, context: &Context) { + pub async fn disconnect(&mut self, context: &Context) { self.unsetup_handle(context).await; self.free_connect_params().await; } - pub async fn fetch(&self, context: &Context, watch_folder: &str) -> Result<()> { + pub async fn fetch(&mut self, context: &Context, watch_folder: &str) -> Result<()> { if !context.sql.is_open().await { // probably shutdown return Err(Error::InTeardown); @@ -511,7 +517,7 @@ impl Imap { /// return Result with (uid_validity, last_seen_uid) tuple. pub(crate) async fn select_with_uidvalidity( - &self, + &mut self, context: &Context, folder: &str, ) -> Result<(u32, u32)> { @@ -520,7 +526,7 @@ impl Imap { // compare last seen UIDVALIDITY against the current one let (uid_validity, last_seen_uid) = self.get_config_last_seen_uid(context, &folder).await; - let config = self.config.read().await; + let config = &mut self.config; let mailbox = config .selected_mailbox .as_ref() @@ -561,7 +567,7 @@ impl Imap { context, "IMAP folder has no uid_next, fall back to fetching" ); - if let Some(ref mut session) = &mut *self.session.lock().await { + if let Some(ref mut session) = &mut self.session { // note that we use fetch by sequence number // and thus we only need to get exactly the // last-index message. @@ -598,7 +604,7 @@ impl Imap { } async fn fetch_new_messages>( - &self, + &mut self, context: &Context, folder: S, ) -> Result { @@ -613,9 +619,10 @@ impl Imap { // prefetch info from all unfetched mails let mut new_last_seen_uid = last_seen_uid; - let mut read_errors = 0; + let mut read_errors: usize = 0; - if let Some(ref mut session) = &mut *self.session.lock().await { + let mut uids = Vec::new(); + if let Some(ref mut session) = &mut self.session { // fetch messages with larger UID than the last one seen // `(UID FETCH lastseenuid+1:*)`, see RFC 4549 let set = format!("{}:*", last_seen_uid + 1); @@ -646,9 +653,9 @@ impl Imap { continue; } read_cnt += 1; - let headers = get_fetch_headers(&fetch)?; let message_id = prefetch_get_message_id(&headers).unwrap_or_default(); + if precheck_imf(context, &message_id, folder.as_ref(), cur_uid).await { // we know the message-id already or don't want the message otherwise. info!( @@ -675,26 +682,31 @@ impl Imap { ); } else { // check passed, go fetch the rest - if let Err(err) = self.fetch_single_msg(context, &folder, cur_uid).await { - info!( - context, - "Read error for message {} from \"{}\", trying over later: {}.", - message_id, - folder.as_ref(), - err - ); - read_errors += 1; - } + uids.push((cur_uid, message_id)); } } - if read_errors == 0 { - new_last_seen_uid = cur_uid; - } } } else { return Err(Error::NoConnection); }; + for (cur_uid, message_id) in uids.into_iter() { + if let Err(err) = self.fetch_single_msg(context, &folder, cur_uid).await { + info!( + context, + "Read error for message {} from \"{}\", trying over later: {}.", + message_id, + folder.as_ref(), + err + ); + read_errors += 1; + } + + if read_errors == 0 { + new_last_seen_uid = cur_uid; + } + } + if new_last_seen_uid > last_seen_uid { self.set_config_last_seen_uid(context, &folder, uid_validity, new_last_seen_uid) .await; @@ -743,25 +755,24 @@ impl Imap { /// if no database entries are created. If the function returns an /// error, the caller should try again later. async fn fetch_single_msg>( - &self, + &mut self, context: &Context, folder: S, server_uid: u32, ) -> Result<()> { - if !self.is_connected().await { + if !self.is_connected() { return Err(Error::Other("Not connected".to_string())); } let set = format!("{}", server_uid); - let mut session_lock = self.session.lock().await; - let mut msgs = if let Some(ref mut session) = &mut *session_lock { + let mut msgs = if let Some(ref mut session) = &mut self.session { match session.uid_fetch(set, BODY_FLAGS).await { Ok(msgs) => msgs, Err(err) => { // TODO maybe differentiate between IO and input/parsing problems // so we don't reconnect if we have a (rare) input/output parsing problem? - self.trigger_reconnect(); + self.should_reconnect = true; warn!( context, "Error on fetching message #{} from folder \"{}\"; error={}.", @@ -810,11 +821,11 @@ impl Imap { } pub async fn can_move(&self) -> bool { - self.config.read().await.can_move + self.config.can_move } pub async fn mv( - &self, + &mut self, context: &Context, folder: &str, uid: u32, @@ -843,7 +854,7 @@ impl Imap { let display_folder_id = format!("{}/{}", folder, uid); if self.can_move().await { - if let Some(ref mut session) = &mut *self.session.lock().await { + if let Some(ref mut session) = &mut self.session { match session.uid_mv(&set, &dest_folder).await { Ok(_) => { emit_event!( @@ -879,7 +890,7 @@ impl Imap { ); } - if let Some(ref mut session) = &mut *self.session.lock().await { + if let Some(ref mut session) = &mut self.session { if let Err(err) = session.uid_copy(&set, &dest_folder).await { warn!(context, "Could not copy message: {}", err); return ImapActionResult::Failed; @@ -899,7 +910,7 @@ impl Imap { ); ImapActionResult::Failed } else { - self.config.write().await.selected_folder_needs_expunge = true; + self.config.selected_folder_needs_expunge = true; emit_event!( context, Event::ImapMessageMoved(format!( @@ -911,7 +922,7 @@ impl Imap { } } - async fn add_flag_finalized(&self, context: &Context, server_uid: u32, flag: &str) -> bool { + 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. @@ -925,7 +936,7 @@ impl Imap { } async fn add_flag_finalized_with_set( - &self, + &mut self, context: &Context, uid_set: &str, flag: &str, @@ -933,7 +944,7 @@ impl Imap { if self.should_reconnect() { return false; } - if let Some(ref mut session) = &mut *self.session.lock().await { + if let Some(ref mut session) = &mut self.session { let query = format!("+FLAGS ({})", flag); match session.uid_store(uid_set, &query).await { Ok(_) => {} @@ -951,7 +962,7 @@ impl Imap { } pub async fn prepare_imap_operation_on_msg( - &self, + &mut self, context: &Context, folder: &str, uid: u32, @@ -959,7 +970,7 @@ impl Imap { if uid == 0 { return Some(ImapActionResult::Failed); } - if !self.is_connected().await { + if !self.is_connected() { // currently jobs are only performed on the INBOX thread // TODO: make INBOX/SENT/MVBOX perform the jobs on their // respective folders to avoid select_folder network traffic @@ -990,7 +1001,12 @@ impl Imap { } } - pub async fn set_seen(&self, context: &Context, folder: &str, uid: u32) -> ImapActionResult { + pub async fn set_seen( + &mut self, + context: &Context, + folder: &str, + uid: u32, + ) -> ImapActionResult { if let Some(imapresult) = self .prepare_imap_operation_on_msg(context, folder, uid) .await @@ -1012,7 +1028,7 @@ impl Imap { } pub async fn delete_msg( - &self, + &mut self, context: &Context, message_id: &str, folder: &str, @@ -1031,7 +1047,7 @@ impl Imap { // double-check that we are deleting the correct message-id // this comes at the expense of another imap query - if let Some(ref mut session) = &mut *self.session.lock().await { + if let Some(ref mut session) = &mut self.session { match session.uid_fetch(set, DELETE_CHECK_FLAGS).await { Ok(mut msgs) => { let fetch = if let Some(Ok(fetch)) = msgs.next().await { @@ -1086,13 +1102,13 @@ impl Imap { display_imap_id, message_id )) ); - self.config.write().await.selected_folder_needs_expunge = true; + self.config.selected_folder_needs_expunge = true; ImapActionResult::Success } } pub async fn ensure_configured_folders( - &self, + &mut self, context: &Context, create_mvbox: bool, ) -> Result<()> { @@ -1106,12 +1122,12 @@ impl Imap { return Ok(()); } - if !self.is_connected().await { + if !self.is_connected() { return Err(Error::NoConnection); } info!(context, "Configuring IMAP-folders."); - if let Some(ref mut session) = &mut *self.session.lock().await { + if let Some(ref mut session) = &mut self.session { let mut folders = match session.list(Some(""), Some("*")).await { Ok(f) => f, Err(err) => { @@ -1121,7 +1137,7 @@ impl Imap { let mut sentbox_folder = None; let mut mvbox_folder = None; - let delimiter = self.config.read().await.imap_delimiter; + let delimiter = self.config.imap_delimiter; let fallback_folder = format!("INBOX{}DeltaChat", delimiter); while let Some(folder) = folders.next().await { @@ -1231,7 +1247,7 @@ impl Imap { // } // } - pub async fn empty_folder(&self, context: &Context, folder: &str) { + pub async fn empty_folder(&mut self, context: &Context, folder: &str) { info!(context, "emptying folder {}", folder); // we want to report all error to the user @@ -1261,7 +1277,7 @@ impl Imap { } // we now trigger expunge to actually delete messages - self.config.write().await.selected_folder_needs_expunge = true; + self.config.selected_folder_needs_expunge = true; match self.select_folder::(context, None).await { Ok(()) => { emit_event!(context, Event::ImapFolderEmptied(folder.to_string())); diff --git a/src/imap/select_folder.rs b/src/imap/select_folder.rs index 36c2dbf6b..f14802113 100644 --- a/src/imap/select_folder.rs +++ b/src/imap/select_folder.rs @@ -26,14 +26,13 @@ impl Imap { /// select a folder, possibly update uid_validity and, if needed, /// expunge the folder to remove delete-marked messages. pub(super) async fn select_folder>( - &self, + &mut self, context: &Context, folder: Option, ) -> Result<()> { - if self.session.lock().await.is_none() { - let mut cfg = self.config.write().await; - cfg.selected_folder = None; - cfg.selected_folder_needs_expunge = false; + if self.session.is_none() { + self.config.selected_folder = None; + self.config.selected_folder_needs_expunge = false; self.trigger_reconnect(); return Err(Error::NoSession); } @@ -41,7 +40,7 @@ impl Imap { // if there is a new folder and the new folder is equal to the selected one, there's nothing to do. // if there is _no_ new folder, we continue as we might want to expunge below. if let Some(ref folder) = folder { - if let Some(ref selected_folder) = self.config.read().await.selected_folder { + if let Some(ref selected_folder) = self.config.selected_folder { if folder.as_ref() == selected_folder { return Ok(()); } @@ -49,14 +48,14 @@ impl Imap { } // deselect existing folder, if needed (it's also done implicitly by SELECT, however, without EXPUNGE then) - let needs_expunge = { self.config.read().await.selected_folder_needs_expunge }; + let needs_expunge = { self.config.selected_folder_needs_expunge }; if needs_expunge { - if let Some(ref folder) = self.config.read().await.selected_folder { + if let Some(ref folder) = self.config.selected_folder { info!(context, "Expunge messages in \"{}\".", folder); // A CLOSE-SELECT is considerably faster than an EXPUNGE-SELECT, see // https://tools.ietf.org/html/rfc3501#section-6.4.2 - if let Some(ref mut session) = &mut *self.session.lock().await { + if let Some(ref mut session) = &mut self.session { match session.close().await { Ok(_) => { info!(context, "close/expunge succeeded"); @@ -70,12 +69,12 @@ impl Imap { return Err(Error::NoSession); } } - self.config.write().await.selected_folder_needs_expunge = false; + self.config.selected_folder_needs_expunge = false; } // select new folder if let Some(ref folder) = folder { - if let Some(ref mut session) = &mut *self.session.lock().await { + if let Some(ref mut session) = &mut self.session { let res = session.select(folder).await; // https://tools.ietf.org/html/rfc3501#section-6.3.1 @@ -84,21 +83,20 @@ impl Imap { match res { Ok(mailbox) => { - let mut config = self.config.write().await; - config.selected_folder = Some(folder.as_ref().to_string()); - config.selected_mailbox = Some(mailbox); + self.config.selected_folder = Some(folder.as_ref().to_string()); + self.config.selected_mailbox = Some(mailbox); Ok(()) } Err(async_imap::error::Error::ConnectionLost) => { self.trigger_reconnect(); - self.config.write().await.selected_folder = None; + self.config.selected_folder = None; Err(Error::ConnectionLost) } Err(async_imap::error::Error::Validate(_)) => { Err(Error::BadFolderName(folder.as_ref().to_string())) } Err(err) => { - self.config.write().await.selected_folder = None; + self.config.selected_folder = None; self.trigger_reconnect(); Err(Error::Other(err.to_string())) } diff --git a/src/job.rs b/src/job.rs index edc9c2342..92f3b08d5 100644 --- a/src/job.rs +++ b/src/job.rs @@ -185,124 +185,126 @@ impl Job { F: FnOnce() -> Fut, Fut: Future>, { - // hold the smtp lock during sending of a job and - // its ok/error response processing. Note that if a message - // was sent we need to mark it in the database ASAP as we - // otherwise might send it twice. - if std::env::var(crate::DCC_MIME_DEBUG).is_ok() { - info!(context, "smtp-sending out mime message:"); - println!("{}", String::from_utf8_lossy(&message)); - } - match context - .smtp - .send(context, recipients, message, job_id) - .await - { - Err(crate::smtp::send::Error::SendError(err)) => { - // Remote error, retry later. - warn!(context, "SMTP failed to send: {}", err); - self.pending_error = Some(err.to_string()); + unimplemented!(); + // // hold the smtp lock during sending of a job and + // // its ok/error response processing. Note that if a message + // // was sent we need to mark it in the database ASAP as we + // // otherwise might send it twice. + // if std::env::var(crate::DCC_MIME_DEBUG).is_ok() { + // info!(context, "smtp-sending out mime message:"); + // println!("{}", String::from_utf8_lossy(&message)); + // } + // match context + // .smtp + // .send(context, recipients, message, job_id) + // .await + // { + // Err(crate::smtp::send::Error::SendError(err)) => { + // // Remote error, retry later. + // warn!(context, "SMTP failed to send: {}", err); + // self.pending_error = Some(err.to_string()); - let res = match err { - async_smtp::smtp::error::Error::Permanent(_) => { - Status::Finished(Err(format_err!("Permanent SMTP error: {}", err))) - } - async_smtp::smtp::error::Error::Transient(_) => { - // We got a transient 4xx response from SMTP server. - // Give some time until the server-side error maybe goes away. - Status::RetryLater - } - _ => { - if context.smtp.has_maybe_stale_connection().await { - info!(context, "stale connection? immediately reconnecting"); - Status::RetryNow - } else { - Status::RetryLater - } - } - }; + // let res = match err { + // async_smtp::smtp::error::Error::Permanent(_) => { + // Status::Finished(Err(format_err!("Permanent SMTP error: {}", err))) + // } + // async_smtp::smtp::error::Error::Transient(_) => { + // // We got a transient 4xx response from SMTP server. + // // Give some time until the server-side error maybe goes away. + // Status::RetryLater + // } + // _ => { + // if context.smtp.has_maybe_stale_connection().await { + // info!(context, "stale connection? immediately reconnecting"); + // Status::RetryNow + // } else { + // Status::RetryLater + // } + // } + // }; - // this clears last_success info - context.smtp.disconnect().await; + // // this clears last_success info + // context.smtp.disconnect().await; - res - } - Err(crate::smtp::send::Error::EnvelopeError(err)) => { - // Local error, job is invalid, do not retry. - context.smtp.disconnect().await; - warn!(context, "SMTP job is invalid: {}", err); - Status::Finished(Err(Error::SmtpError(err))) - } - Err(crate::smtp::send::Error::NoTransport) => { - // Should never happen. - // It does not even make sense to disconnect here. - error!(context, "SMTP job failed because SMTP has no transport"); - Status::Finished(Err(format_err!("SMTP has not transport"))) - } - Ok(()) => { - job_try!(success_cb().await); - Status::Finished(Ok(())) - } - } + // res + // } + // Err(crate::smtp::send::Error::EnvelopeError(err)) => { + // // Local error, job is invalid, do not retry. + // context.smtp.disconnect().await; + // warn!(context, "SMTP job is invalid: {}", err); + // Status::Finished(Err(Error::SmtpError(err))) + // } + // Err(crate::smtp::send::Error::NoTransport) => { + // // Should never happen. + // // It does not even make sense to disconnect here. + // error!(context, "SMTP job failed because SMTP has no transport"); + // Status::Finished(Err(format_err!("SMTP has not transport"))) + // } + // Ok(()) => { + // job_try!(success_cb().await); + // Status::Finished(Ok(())) + // } + // } } async fn send_msg_to_smtp(&mut self, context: &Context) -> Status { - // connect to SMTP server, if not yet done - if !context.smtp.is_connected().await { - let loginparam = LoginParam::from_database(context, "configured_").await; - if let Err(err) = context.smtp.connect(context, &loginparam).await { - warn!(context, "SMTP connection failure: {:?}", err); - return Status::RetryLater; - } - } + unimplemented!(); + // connect to// SMTP server, if not yet done + // if !context.smtp.is_connected().await { + // let loginparam = LoginParam::from_database(context, "configured_").await; + // if let Err(err) = context.smtp.connect(context, &loginparam).await { + // warn!(context, "SMTP connection failure: {:?}", err); + // return Status::RetryLater; + // } + // } - 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"))); - 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 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"))); + // 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_list = recipients - .split('\x1e') - .filter_map( - |addr| match async_smtp::EmailAddress::new(addr.to_string()) { - Ok(addr) => Some(addr), - Err(err) => { - warn!(context, "invalid recipient: {} {:?}", addr, err); - None - } - }, - ) - .collect::>(); + // let recipients_list = recipients + // .split('\x1e') + // .filter_map( + // |addr| match async_smtp::EmailAddress::new(addr.to_string()) { + // Ok(addr) => Some(addr), + // Err(err) => { + // warn!(context, "invalid recipient: {} {:?}", addr, err); + // None + // } + // }, + // ) + // .collect::>(); - /* if there is a msg-id and it does not exist in the db, cancel sending. - this happends if dc_delete_msgs() was called - before the generated mime was sent out */ - if 0 != self.foreign_id && !message::exists(context, MsgId::new(self.foreign_id)).await { - return Status::Finished(Err(format_err!( - "Not sending Message {} as it was deleted", - self.foreign_id - ))); - }; + // /* if there is a msg-id and it does not exist in the db, cancel sending. + // this happends if dc_delete_msgs() was called + // before the generated mime was sent out */ + // if 0 != self.foreign_id && !message::exists(context, MsgId::new(self.foreign_id)).await { + // return Status::Finished(Err(format_err!( + // "Not sending Message {} as it was deleted", + // self.foreign_id + // ))); + // }; - let foreign_id = self.foreign_id; - self.smtp_send(context, recipients_list, body, self.job_id, || { - async move { - // smtp success, update db ASAP, then delete smtp file - if 0 != foreign_id { - set_delivered(context, MsgId::new(foreign_id)).await; - } - // now also delete the generated file - dc_delete_file(context, filename).await; - Ok(()) - } - }) - .await + // let foreign_id = self.foreign_id; + // self.smtp_send(context, recipients_list, body, self.job_id, || { + // async move { + // // smtp success, update db ASAP, then delete smtp file + // if 0 != foreign_id { + // set_delivered(context, MsgId::new(foreign_id)).await; + // } + // // now also delete the generated file + // dc_delete_file(context, filename).await; + // Ok(()) + // } + // }) + // .await } /// Get `SendMdn` jobs with foreign_id equal to `contact_id` excluding the `job_id` job. @@ -349,228 +351,234 @@ impl Job { } async fn send_mdn(&mut self, context: &Context) -> Status { - if !context.get_config_bool(Config::MdnsEnabled).await { - // User has disabled MDNs after job scheduling but before - // execution. - return Status::Finished(Err(format_err!("MDNs are disabled"))); - } + unimplemented!(); + // if !context.get_config_bool(Config::MdnsEnabled).await { + // // User has disabled MDNs after job scheduling but before + // // execution. + // return Status::Finished(Err(format_err!("MDNs are disabled"))); + // } - let contact_id = self.foreign_id; - let contact = job_try!(Contact::load_from_db(context, contact_id).await); - if contact.is_blocked() { - return Status::Finished(Err(format_err!("Contact is blocked"))); - } + // let contact_id = self.foreign_id; + // let contact = job_try!(Contact::load_from_db(context, contact_id).await); + // if contact.is_blocked() { + // return Status::Finished(Err(format_err!("Contact is blocked"))); + // } - let msg_id = if let Some(msg_id) = self.param.get_msg_id() { - msg_id - } else { - return Status::Finished(Err(format_err!( - "SendMdn job has invalid parameters: {}", - self.param - ))); - }; + // let msg_id = if let Some(msg_id) = self.param.get_msg_id() { + // msg_id + // } else { + // return Status::Finished(Err(format_err!( + // "SendMdn job has invalid parameters: {}", + // self.param + // ))); + // }; - // Try to aggregate other SendMdn jobs and send a combined MDN. - let (additional_job_ids, additional_rfc724_mids) = self - .get_additional_mdn_jobs(context, contact_id) - .await - .unwrap_or_default(); + // // Try to aggregate other SendMdn jobs and send a combined MDN. + // let (additional_job_ids, additional_rfc724_mids) = self + // .get_additional_mdn_jobs(context, contact_id) + // .await + // .unwrap_or_default(); - if !additional_rfc724_mids.is_empty() { - info!( - context, - "SendMdn job: aggregating {} additional MDNs", - additional_rfc724_mids.len() - ) - } + // if !additional_rfc724_mids.is_empty() { + // info!( + // context, + // "SendMdn job: aggregating {} additional MDNs", + // additional_rfc724_mids.len() + // ) + // } - let msg = job_try!(Message::load_from_db(context, msg_id).await); - let mimefactory = - job_try!(MimeFactory::from_mdn(context, &msg, additional_rfc724_mids).await); - let rendered_msg = job_try!(mimefactory.render().await); - let body = rendered_msg.message; + // let msg = job_try!(Message::load_from_db(context, msg_id).await); + // let mimefactory = + // job_try!(MimeFactory::from_mdn(context, &msg, additional_rfc724_mids).await); + // let rendered_msg = job_try!(mimefactory.render().await); + // let body = rendered_msg.message; - let addr = contact.get_addr(); - let recipient = job_try!(async_smtp::EmailAddress::new(addr.to_string()) - .map_err(|err| format_err!("invalid recipient: {} {:?}", addr, err))); - let recipients = vec![recipient]; + // let addr = contact.get_addr(); + // let recipient = job_try!(async_smtp::EmailAddress::new(addr.to_string()) + // .map_err(|err| format_err!("invalid recipient: {} {:?}", addr, err))); + // let recipients = vec![recipient]; - // connect to SMTP server, if not yet done - if !context.smtp.is_connected().await { - let loginparam = LoginParam::from_database(context, "configured_").await; - if let Err(err) = context.smtp.connect(context, &loginparam).await { - warn!(context, "SMTP connection failure: {:?}", err); - return Status::RetryLater; - } - } + // // connect to SMTP server, if not yet done + // if !context.smtp.is_connected().await { + // let loginparam = LoginParam::from_database(context, "configured_").await; + // if let Err(err) = context.smtp.connect(context, &loginparam).await { + // warn!(context, "SMTP connection failure: {:?}", err); + // return Status::RetryLater; + // } + // } - self.smtp_send(context, recipients, body, self.job_id, || { - async move { - // Remove additional SendMdn jobs we have aggregated into this one. - job::kill_ids(context, &additional_job_ids).await?; - Ok(()) - } - }) - .await + // self.smtp_send(context, recipients, body, self.job_id, || { + // async move { + // // Remove additional SendMdn jobs we have aggregated into this one. + // job::kill_ids(context, &additional_job_ids).await?; + // Ok(()) + // } + // }) + // .await } async fn move_msg(&mut self, context: &Context) -> Status { - let imap_inbox = &context.inbox_thread.imap; + unimplemented!(); + // let imap_inbox = &context.inbox_thread.imap; - let msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)).await); + // let msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)).await); - if let Err(err) = imap_inbox.ensure_configured_folders(context, true).await { - warn!(context, "could not configure folders: {:?}", err); - return Status::RetryLater; - } - let dest_folder = context - .sql - .get_raw_config(context, "configured_mvbox_folder") - .await; + // if let Err(err) = imap_inbox.ensure_configured_folders(context, true).await { + // warn!(context, "could not configure folders: {:?}", err); + // return Status::RetryLater; + // } + // let dest_folder = context + // .sql + // .get_raw_config(context, "configured_mvbox_folder") + // .await; - if let Some(dest_folder) = dest_folder { - let server_folder = msg.server_folder.as_ref().unwrap(); - let mut dest_uid = 0; + // if let Some(dest_folder) = dest_folder { + // let server_folder = msg.server_folder.as_ref().unwrap(); + // let mut dest_uid = 0; - match imap_inbox - .mv( - context, - server_folder, - msg.server_uid, - &dest_folder, - &mut dest_uid, - ) - .await - { - ImapActionResult::RetryLater => Status::RetryLater, - ImapActionResult::Success => { - message::update_server_uid(context, &msg.rfc724_mid, &dest_folder, dest_uid) - .await; - Status::Finished(Ok(())) - } - ImapActionResult::Failed => { - Status::Finished(Err(format_err!("IMAP action failed"))) - } - ImapActionResult::AlreadyDone => Status::Finished(Ok(())), - } - } else { - Status::Finished(Err(format_err!("No mvbox folder configured"))) - } + // match imap_inbox + // .mv( + // context, + // server_folder, + // msg.server_uid, + // &dest_folder, + // &mut dest_uid, + // ) + // .await + // { + // ImapActionResult::RetryLater => Status::RetryLater, + // ImapActionResult::Success => { + // message::update_server_uid(context, &msg.rfc724_mid, &dest_folder, dest_uid) + // .await; + // Status::Finished(Ok(())) + // } + // ImapActionResult::Failed => { + // Status::Finished(Err(format_err!("IMAP action failed"))) + // } + // ImapActionResult::AlreadyDone => Status::Finished(Ok(())), + // } + // } else { + // Status::Finished(Err(format_err!("No mvbox folder configured"))) + // } } async fn delete_msg_on_imap(&mut self, context: &Context) -> Status { - let imap_inbox = &context.inbox_thread.imap; + unimplemented!(); + // let imap_inbox = &context.inbox_thread.imap; - let mut msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)).await); + // let mut msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)).await); - if !msg.rfc724_mid.is_empty() { - if message::rfc724_mid_cnt(context, &msg.rfc724_mid).await > 1 { - info!( - context, - "The message is deleted from the server when all parts are deleted.", - ); - } else { - /* if this is the last existing part of the message, - we delete the message from the server */ - let mid = msg.rfc724_mid; - let server_folder = msg.server_folder.as_ref().unwrap(); - let res = imap_inbox - .delete_msg(context, &mid, server_folder, &mut msg.server_uid) - .await; - if res == ImapActionResult::RetryLater { - // XXX RetryLater is converted to RetryNow here - return Status::RetryNow; - } - } - Message::delete_from_db(context, msg.id).await; - Status::Finished(Ok(())) - } else { - /* eg. device messages have no Message-ID */ - Status::Finished(Ok(())) - } + // if !msg.rfc724_mid.is_empty() { + // if message::rfc724_mid_cnt(context, &msg.rfc724_mid).await > 1 { + // info!( + // context, + // "The message is deleted from the server when all parts are deleted.", + // ); + // } else { + // /* if this is the last existing part of the message, + // we delete the message from the server */ + // let mid = msg.rfc724_mid; + // let server_folder = msg.server_folder.as_ref().unwrap(); + // let res = imap_inbox + // .delete_msg(context, &mid, server_folder, &mut msg.server_uid) + // .await; + // if res == ImapActionResult::RetryLater { + // // XXX RetryLater is converted to RetryNow here + // return Status::RetryNow; + // } + // } + // Message::delete_from_db(context, msg.id).await; + // Status::Finished(Ok(())) + // } else { + // /* eg. device messages have no Message-ID */ + // Status::Finished(Ok(())) + // } } async fn empty_server(&mut self, context: &Context) -> Status { - let imap_inbox = &context.inbox_thread.imap; - if self.foreign_id & DC_EMPTY_MVBOX > 0 { - if let Some(mvbox_folder) = context - .sql - .get_raw_config(context, "configured_mvbox_folder") - .await - { - imap_inbox.empty_folder(context, &mvbox_folder).await; - } - } - if self.foreign_id & DC_EMPTY_INBOX > 0 { - imap_inbox.empty_folder(context, "INBOX").await; - } - Status::Finished(Ok(())) + unimplemented!(); + // let imap_inbox = &context.inbox_thread.imap; + // if self.foreign_id & DC_EMPTY_MVBOX > 0 { + // if let Some(mvbox_folder) = context + // .sql + // .get_raw_config(context, "configured_mvbox_folder") + // .await + // { + // imap_inbox.empty_folder(context, &mvbox_folder).await; + // } + // } + // if self.foreign_id & DC_EMPTY_INBOX > 0 { + // imap_inbox.empty_folder(context, "INBOX").await; + // } + // Status::Finished(Ok(())) } async fn markseen_msg_on_imap(&mut self, context: &Context) -> Status { - let imap_inbox = &context.inbox_thread.imap; + unimplemented!(); + // let imap_inbox = &context.inbox_thread.imap; - let msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)).await); + // let msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)).await); - let folder = msg.server_folder.as_ref().unwrap(); - match imap_inbox.set_seen(context, folder, msg.server_uid).await { - ImapActionResult::RetryLater => Status::RetryLater, - ImapActionResult::AlreadyDone => Status::Finished(Ok(())), - ImapActionResult::Success | ImapActionResult::Failed => { - // XXX the message might just have been moved - // we want to send out an MDN anyway - // The job will not be retried so locally - // there is no risk of double-sending MDNs. - if msg.param.get_bool(Param::WantsMdn).unwrap_or_default() - && context.get_config_bool(Config::MdnsEnabled).await - { - if let Err(err) = send_mdn(context, &msg).await { - warn!(context, "could not send out mdn for {}: {}", msg.id, err); - return Status::Finished(Err(err)); - } - } - Status::Finished(Ok(())) - } - } + // let folder = msg.server_folder.as_ref().unwrap(); + // match imap_inbox.set_seen(context, folder, msg.server_uid).await { + // ImapActionResult::RetryLater => Status::RetryLater, + // ImapActionResult::AlreadyDone => Status::Finished(Ok(())), + // ImapActionResult::Success | ImapActionResult::Failed => { + // // XXX the message might just have been moved + // // we want to send out an MDN anyway + // // The job will not be retried so locally + // // there is no risk of double-sending MDNs. + // if msg.param.get_bool(Param::WantsMdn).unwrap_or_default() + // && context.get_config_bool(Config::MdnsEnabled).await + // { + // if let Err(err) = send_mdn(context, &msg).await { + // warn!(context, "could not send out mdn for {}: {}", msg.id, err); + // return Status::Finished(Err(err)); + // } + // } + // Status::Finished(Ok(())) + // } + // } } async fn markseen_mdn_on_imap(&mut self, context: &Context) -> Status { - let folder = self - .param - .get(Param::ServerFolder) - .unwrap_or_default() - .to_string(); - let uid = self.param.get_int(Param::ServerUid).unwrap_or_default() as u32; - let imap_inbox = &context.inbox_thread.imap; - if imap_inbox.set_seen(context, &folder, uid).await == ImapActionResult::RetryLater { - return Status::RetryLater; - } - if self.param.get_bool(Param::AlsoMove).unwrap_or_default() { - if let Err(err) = imap_inbox.ensure_configured_folders(context, true).await { - warn!(context, "configuring folders failed: {:?}", err); - return Status::RetryLater; - } - let dest_folder = context - .sql - .get_raw_config(context, "configured_mvbox_folder") - .await; - if let Some(dest_folder) = dest_folder { - let mut dest_uid = 0; - if ImapActionResult::RetryLater - == imap_inbox - .mv(context, &folder, uid, &dest_folder, &mut dest_uid) - .await - { - Status::RetryLater - } else { - Status::Finished(Ok(())) - } - } else { - Status::Finished(Err(format_err!("MVBOX is not configured"))) - } - } else { - Status::Finished(Ok(())) - } + unimplemented!(); + // let folder = self + // .param + // .get(Param::ServerFolder) + // .unwrap_or_default() + // .to_string(); + // let uid = self.param.get_int(Param::ServerUid).unwrap_or_default() as u32; + // let imap_inbox = &context.inbox_thread.imap; + // if imap_inbox.set_seen(context, &folder, uid).await == ImapActionResult::RetryLater { + // return Status::RetryLater; + // } + // if self.param.get_bool(Param::AlsoMove).unwrap_or_default() { + // if let Err(err) = imap_inbox.ensure_configured_folders(context, true).await { + // warn!(context, "configuring folders failed: {:?}", err); + // return Status::RetryLater; + // } + // let dest_folder = context + // .sql + // .get_raw_config(context, "configured_mvbox_folder") + // .await; + // if let Some(dest_folder) = dest_folder { + // let mut dest_uid = 0; + // if ImapActionResult::RetryLater + // == imap_inbox + // .mv(context, &folder, uid, &dest_folder, &mut dest_uid) + // .await + // { + // Status::RetryLater + // } else { + // Status::Finished(Ok(())) + // } + // } else { + // Status::Finished(Err(format_err!("MVBOX is not configured"))) + // } + // } else { + // Status::Finished(Ok(())) + // } } } @@ -599,116 +607,126 @@ pub async fn kill_ids(context: &Context, job_ids: &[u32]) -> sql::Result<()> { } pub async fn perform_inbox_fetch(context: &Context) { - let use_network = context.get_config_bool(Config::InboxWatch).await; + unimplemented!(); + // let use_network = context.get_config_bool(Config::InboxWatch).await; - context.inbox_thread.fetch(context, use_network).await; + // context.inbox_thread.fetch(context, use_network).await; } pub async fn perform_mvbox_fetch(context: &Context) { - let use_network = context.get_config_bool(Config::MvboxWatch).await; + unimplemented!(); + // let use_network = context.get_config_bool(Config::MvboxWatch).await; - context.mvbox_thread.fetch(context, use_network).await; + // context.mvbox_thread.fetch(context, use_network).await; } pub async fn perform_sentbox_fetch(context: &Context) { - let use_network = context.get_config_bool(Config::SentboxWatch).await; + unimplemented!(); + // let use_network = context.get_config_bool(Config::SentboxWatch).await; - context.sentbox_thread.fetch(context, use_network).await; + // context.sentbox_thread.fetch(context, use_network).await; } pub async fn perform_inbox_idle(context: &Context) { - if context - .perform_inbox_jobs_needed - .load(std::sync::atomic::Ordering::Relaxed) - { - info!( - context, - "INBOX-IDLE will not be started because of waiting jobs." - ); - return; - } - let use_network = context.get_config_bool(Config::InboxWatch).await; + unimplemented!(); + // if context + // .perform_inbox_jobs_needed + // .load(std::sync::atomic::Ordering::Relaxed) + // { + // info!( + // context, + // "INBOX-IDLE will not be started because of waiting jobs." + // ); + // return; + // } + // let use_network = context.get_config_bool(Config::InboxWatch).await; - context.inbox_thread.idle(context, use_network).await; + // context.inbox_thread.idle(context, use_network).await; } pub async fn perform_mvbox_idle(context: &Context) { - let use_network = context.get_config_bool(Config::MvboxWatch).await; + unimplemented!(); + // let use_network = context.get_config_bool(Config::MvboxWatch).await; - context.mvbox_thread.idle(context, use_network).await; -} + // context.mvbox_thread.idle(context, use_network).await; + // } -pub async fn perform_sentbox_idle(context: &Context) { - let use_network = context.get_config_bool(Config::SentboxWatch).await; + // pub async fn perform_sentbox_idle(context: &Context) { + // let use_network = context.get_config_bool(Config::SentboxWatch).await; - context.sentbox_thread.idle(context, use_network).await; + // context.sentbox_thread.idle(context, use_network).await; } pub async fn interrupt_inbox_idle(context: &Context) { - info!(context, "interrupt_inbox_idle called"); - // we do not block on trying to obtain the thread lock - // because we don't know in which state the thread is. - // If it's currently fetching then we can not get the lock - // but we flag it for checking jobs so that idle will be skipped. - if !context.inbox_thread.try_interrupt_idle(context).await { - context - .perform_inbox_jobs_needed - .store(true, std::sync::atomic::Ordering::Relaxed); - warn!(context, "could not interrupt idle"); - } + unimplemented!(); + // info!(context, "interrupt_inbox_idle called"); + // // we do not block on trying to obtain the thread lock + // // because we don't know in which state the thread is. + // // If it's currently fetching then we can not get the lock + // // but we flag it for checking jobs so that idle will be skipped. + // if !context.inbox_thread.try_interrupt_idle(context).await { + // context + // .perform_inbox_jobs_needed + // .store(true, std::sync::atomic::Ordering::Relaxed); + // warn!(context, "could not interrupt idle"); + // } } pub async fn interrupt_mvbox_idle(context: &Context) { - context.mvbox_thread.interrupt_idle(context).await; + unimplemented!(); + // context.mvbox_thread.interrupt_idle(context).await; } pub async fn interrupt_sentbox_idle(context: &Context) { - context.sentbox_thread.interrupt_idle(context).await; + unimplemented!(); + // context.sentbox_thread.interrupt_idle(context).await; } pub async fn perform_smtp_jobs(context: &Context) { - let probe_smtp_network = { - let state = &mut *context.smtp.state.write().await; + unimplemented!(); + // let probe_smtp_network = { + // let state = &mut *context.smtp.state.write().await; - let probe_smtp_network = state.probe_network; - state.probe_network = false; - state.perform_jobs_needed = PerformJobsNeeded::Not; + // let probe_smtp_network = state.probe_network; + // state.probe_network = false; + // state.perform_jobs_needed = PerformJobsNeeded::Not; - if state.suspended { - info!(context, "SMTP-jobs suspended.",); - return; - } - state.doing_jobs = true; - probe_smtp_network - }; + // if state.suspended { + // info!(context, "SMTP-jobs suspended.",); + // return; + // } + // state.doing_jobs = true; + // probe_smtp_network + // }; - info!(context, "SMTP-jobs started...",); - job_perform(context, Thread::Smtp, probe_smtp_network).await; - info!(context, "SMTP-jobs ended."); + // info!(context, "SMTP-jobs started...",); + // job_perform(context, Thread::Smtp, probe_smtp_network).await; + // info!(context, "SMTP-jobs ended."); - context.smtp.state.write().await.doing_jobs = false; + // context.smtp.state.write().await.doing_jobs = false; } pub async fn perform_smtp_idle(context: &Context) { - info!(context, "SMTP-idle started..."); + unimplemented!(); + // info!(context, "SMTP-idle started..."); - let perform_jobs_needed = context.smtp.state.read().await.perform_jobs_needed.clone(); + // let perform_jobs_needed = context.smtp.state.read().await.perform_jobs_needed.clone(); - match perform_jobs_needed { - PerformJobsNeeded::AtOnce => { - info!( - context, - "SMTP-idle will not be started because of waiting jobs.", - ); - } - PerformJobsNeeded::Not | PerformJobsNeeded::AvoidDos => { - let dur = get_next_wakeup_time(context, Thread::Smtp).await; + // match perform_jobs_needed { + // PerformJobsNeeded::AtOnce => { + // info!( + // context, + // "SMTP-idle will not be started because of waiting jobs.", + // ); + // } + // PerformJobsNeeded::Not | PerformJobsNeeded::AvoidDos => { + // let dur = get_next_wakeup_time(context, Thread::Smtp).await; - context.smtp.notify_receiver.recv().timeout(dur).await.ok(); - } - } + // context.smtp.notify_receiver.recv().timeout(dur).await.ok(); + // } + // } - info!(context, "SMTP-idle ended.",); + // info!(context, "SMTP-idle ended.",); } async fn get_next_wakeup_time(context: &Context, thread: Thread) -> time::Duration { @@ -736,17 +754,18 @@ async fn get_next_wakeup_time(context: &Context, thread: Thread) -> time::Durati } pub async fn maybe_network(context: &Context) { - { - context.smtp.state.write().await.probe_network = true; - context - .probe_imap_network - .store(true, std::sync::atomic::Ordering::Relaxed); - } + unimplemented!(); + // { + // context.smtp.state.write().await.probe_network = true; + // context + // .probe_imap_network + // .store(true, std::sync::atomic::Ordering::Relaxed); + // } - interrupt_smtp_idle(context).await; - interrupt_inbox_idle(context).await; - interrupt_mvbox_idle(context).await; - interrupt_sentbox_idle(context).await; + // interrupt_smtp_idle(context).await; + // interrupt_inbox_idle(context).await; + // interrupt_mvbox_idle(context).await; + // interrupt_sentbox_idle(context).await; } pub async fn action_exists(context: &Context, action: Action) -> bool { @@ -879,123 +898,127 @@ pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> { } pub async fn perform_inbox_jobs(context: &Context) { - info!(context, "dc_perform_inbox_jobs starting.",); + unimplemented!(); + // info!(context, "dc_perform_inbox_jobs starting.",); - let probe_imap_network = context - .probe_imap_network - .load(std::sync::atomic::Ordering::Relaxed); - context - .probe_imap_network - .store(false, std::sync::atomic::Ordering::Relaxed); - context - .perform_inbox_jobs_needed - .store(false, std::sync::atomic::Ordering::Relaxed); + // let probe_imap_network = context + // .probe_imap_network + // .load(std::sync::atomic::Ordering::Relaxed); + // context + // .probe_imap_network + // .store(false, std::sync::atomic::Ordering::Relaxed); + // context + // .perform_inbox_jobs_needed + // .store(false, std::sync::atomic::Ordering::Relaxed); - job_perform(context, Thread::Imap, probe_imap_network).await; - info!(context, "dc_perform_inbox_jobs ended.",); + // job_perform(context, Thread::Imap, probe_imap_network).await; + // info!(context, "dc_perform_inbox_jobs ended.",); } pub async fn perform_mvbox_jobs(context: &Context) { - info!(context, "dc_perform_mbox_jobs EMPTY (for now)."); + unimplemented!(); + // info!(context, "dc_perform_mbox_jobs EMPTY (for now)."); } pub async fn perform_sentbox_jobs(context: &Context) { - info!(context, "dc_perform_sentbox_jobs EMPTY (for now)."); + unimplemented!(); + // info!(context, "dc_perform_sentbox_jobs EMPTY (for now)."); } async fn job_perform(context: &Context, thread: Thread, probe_network: bool) { - while let Some(mut job) = load_next_job(context, thread, probe_network).await { - info!(context, "{}-job {} started...", thread, job); + unimplemented!(); + // while let Some(mut job) = load_next_job(context, thread, probe_network).await { + // info!(context, "{}-job {} started...", thread, job); - // some configuration jobs are "exclusive": - // - they are always executed in the imap-thread and the smtp-thread is suspended during execution - // - they may change the database handle; we do not keep old pointers therefore - // - they can be re-executed one time AT_ONCE, but they are not saved in the database for later execution - if Action::ConfigureImap == job.action || Action::ImexImap == job.action { - job::kill_action(context, job.action).await; - context.sentbox_thread.suspend(context).await; - context.mvbox_thread.suspend(context).await; - suspend_smtp_thread(context, true).await; - } + // // some configuration jobs are "exclusive": + // // - they are always executed in the imap-thread and the smtp-thread is suspended during execution + // // - they may change the database handle; we do not keep old pointers therefore + // // - they can be re-executed one time AT_ONCE, but they are not saved in the database for later execution + // if Action::ConfigureImap == job.action || Action::ImexImap == job.action { + // job::kill_action(context, job.action).await; + // context.sentbox_thread.suspend(context).await; + // context.mvbox_thread.suspend(context).await; + // suspend_smtp_thread(context, true).await; + // } - let try_res = match perform_job_action(context, &mut job, thread, 0).await { - Status::RetryNow => perform_job_action(context, &mut job, thread, 1).await, - x => x, - }; + // let try_res = match perform_job_action(context, &mut job, thread, 0).await { + // Status::RetryNow => perform_job_action(context, &mut job, thread, 1).await, + // x => x, + // }; - if Action::ConfigureImap == job.action || Action::ImexImap == job.action { - context.sentbox_thread.unsuspend(context).await; - context.mvbox_thread.unsuspend(context).await; - suspend_smtp_thread(context, false).await; - break; - } + // if Action::ConfigureImap == job.action || Action::ImexImap == job.action { + // context.sentbox_thread.unsuspend(context).await; + // context.mvbox_thread.unsuspend(context).await; + // suspend_smtp_thread(context, false).await; + // break; + // } - match try_res { - Status::RetryNow | Status::RetryLater => { - let tries = job.tries + 1; + // match try_res { + // Status::RetryNow | Status::RetryLater => { + // let tries = job.tries + 1; - if tries < JOB_RETRIES { - info!( - context, - "{} thread increases job {} tries to {}", thread, job, tries - ); - job.tries = tries; - let time_offset = get_backoff_time_offset(tries); - job.desired_timestamp = time() + time_offset; - job.update(context).await; - info!( - context, - "{}-job #{} not succeeded on try #{}, retry in {} seconds.", - thread, - job.job_id as u32, - tries, - time_offset - ); - if thread == Thread::Smtp && tries < JOB_RETRIES - 1 { - context.smtp.state.write().await.perform_jobs_needed = - PerformJobsNeeded::AvoidDos; - } - } else { - info!( - context, - "{} thread removes job {} as it exhausted {} retries", - thread, - job, - JOB_RETRIES - ); - if job.action == Action::SendMsgToSmtp { - message::set_msg_failed( - context, - MsgId::new(job.foreign_id), - job.pending_error.as_ref(), - ) - .await; - } - job.delete(context).await; - } - if !probe_network { - continue; - } - // on dc_maybe_network() we stop trying here; - // these jobs are already tried once. - // otherwise, we just continue with the next job - // to give other jobs a chance being tried at least once. - break; - } - Status::Finished(res) => { - if let Err(err) = res { - warn!( - context, - "{} removes job {} as it failed with error {:?}", thread, job, err - ); - } else { - info!(context, "{} removes job {} as it succeeded", thread, job); - } + // if tries < JOB_RETRIES { + // info!( + // context, + // "{} thread increases job {} tries to {}", thread, job, tries + // ); + // job.tries = tries; + // let time_offset = get_backoff_time_offset(tries); + // job.desired_timestamp = time() + time_offset; + // job.update(context).await; + // info!( + // context, + // "{}-job #{} not succeeded on try #{}, retry in {} seconds.", + // thread, + // job.job_id as u32, + // tries, + // time_offset + // ); + // if thread == Thread::Smtp && tries < JOB_RETRIES - 1 { + // context.smtp.state.write().await.perform_jobs_needed = + // PerformJobsNeeded::AvoidDos; + // } + // } else { + // info!( + // context, + // "{} thread removes job {} as it exhausted {} retries", + // thread, + // job, + // JOB_RETRIES + // ); + // if job.action == Action::SendMsgToSmtp { + // message::set_msg_failed( + // context, + // MsgId::new(job.foreign_id), + // job.pending_error.as_ref(), + // ) + // .await; + // } + // job.delete(context).await; + // } + // if !probe_network { + // continue; + // } + // // on dc_maybe_network() we stop trying here; + // // these jobs are already tried once. + // // otherwise, we just continue with the next job + // // to give other jobs a chance being tried at least once. + // break; + // } + // Status::Finished(res) => { + // if let Err(err) = res { + // warn!( + // context, + // "{} removes job {} as it failed with error {:?}", thread, job, err + // ); + // } else { + // info!(context, "{} removes job {} as it succeeded", thread, job); + // } - job.delete(context).await; - } - } - } + // job.delete(context).await; + // } + // } + // } } async fn perform_job_action( @@ -1056,15 +1079,16 @@ fn get_backoff_time_offset(tries: u32) -> i64 { } async fn suspend_smtp_thread(context: &Context, suspend: bool) { - context.smtp.state.write().await.suspended = suspend; - if suspend { - loop { - if !context.smtp.state.read().await.doing_jobs { - return; - } - async_std::task::sleep(time::Duration::from_micros(300 * 1000)).await; - } - } + unimplemented!(); + // context.smtp.state.write().await.suspended = suspend; + // if suspend { + // loop { + // if !context.smtp.state.read().await.doing_jobs { + // return; + // } + // async_std::task::sleep(time::Duration::from_micros(300 * 1000)).await; + // } + // } } async fn send_mdn(context: &Context, msg: &Message) -> Result<()> { @@ -1133,12 +1157,13 @@ pub async fn add( } pub async fn interrupt_smtp_idle(context: &Context) { - info!(context, "Interrupting SMTP-idle...",); + unimplemented!(); + // info!(context, "Interrupting SMTP-idle...",); - context.smtp.state.write().await.perform_jobs_needed = PerformJobsNeeded::AtOnce; - context.smtp.notify_sender.send(()).await; + // context.smtp.state.write().await.perform_jobs_needed = PerformJobsNeeded::AtOnce; + // context.smtp.notify_sender.send(()).await; - info!(context, "Interrupting SMTP-idle... ended",); + // info!(context, "Interrupting SMTP-idle... ended",); } /// Load jobs from the database. diff --git a/src/job_thread.rs b/src/job_thread.rs index 2d3f843f9..12d86bc50 100644 --- a/src/job_thread.rs +++ b/src/job_thread.rs @@ -35,7 +35,7 @@ impl JobThread { } } - pub async fn suspend(&self, context: &Context) { + pub async fn suspend(&mut self, context: &Context) { info!(context, "Suspending {}-thread.", self.name,); { self.state.lock().await.suspended = true; @@ -62,7 +62,7 @@ impl JobThread { self.notify_sender.send(()).await; } - pub async fn try_interrupt_idle(&self, context: &Context) -> bool { + pub async fn try_interrupt_idle(&mut self, context: &Context) -> bool { if self.state.lock().await.using_handle { self.interrupt_idle(context).await; return true; @@ -71,7 +71,7 @@ impl JobThread { false } - pub async fn interrupt_idle(&self, context: &Context) { + pub async fn interrupt_idle(&mut self, context: &Context) { { self.state.lock().await.jobs_needed = true; } @@ -85,7 +85,7 @@ impl JobThread { info!(context, "Interrupting {}-IDLE... finished", self.name); } - pub async fn fetch(&self, context: &Context, use_network: bool) { + pub async fn fetch(&mut self, context: &Context, use_network: bool) { { let lock = &*self.state.clone(); let mut state = lock.lock().await; @@ -111,7 +111,7 @@ impl JobThread { } } - async fn connect_and_fetch(&self, context: &Context) -> Result<()> { + async fn connect_and_fetch(&mut self, context: &Context) -> Result<()> { let prefix = format!("{}-fetch", self.name); match self.imap.connect_configured(context).await { Ok(()) => { @@ -153,7 +153,7 @@ impl JobThread { } } - pub async fn idle(&self, context: &Context, use_network: bool) { + pub async fn idle(&mut self, context: &Context, use_network: bool) { { let lock = &*self.state.clone(); let mut state = lock.lock().await; @@ -185,7 +185,7 @@ impl JobThread { let prefix = format!("{}-IDLE", self.name); let do_fake_idle = match self.imap.connect_configured(context).await { Ok(()) => { - if !self.imap.can_idle().await { + if !self.imap.can_idle() { true // we have to do fake_idle } else { let watch_folder = self.get_watch_folder(context).await; diff --git a/src/lib.rs b/src/lib.rs index c9f8aafe9..3d2dd376c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,6 +51,7 @@ pub mod context; mod e2ee; mod imap; pub mod imex; +mod scheduler; #[macro_use] pub mod job; mod job_thread; diff --git a/src/oauth2.rs b/src/oauth2.rs index 8eb0fc286..6bb657882 100644 --- a/src/oauth2.rs +++ b/src/oauth2.rs @@ -84,8 +84,9 @@ pub async fn dc_get_oauth2_access_token( regenerate: bool, ) -> Option { if let Some(oauth2) = Oauth2::from_address(addr) { - let lock = context.oauth2_critical.clone(); - let _l = lock.lock().await; + // TODO: FIXME + // let lock = context.oauth2_critical.clone(); + // let _l = lock.lock().await; // read generated token if !regenerate && !is_expired(context).await { diff --git a/src/scheduler.rs b/src/scheduler.rs new file mode 100644 index 000000000..f010b2359 --- /dev/null +++ b/src/scheduler.rs @@ -0,0 +1,237 @@ +use async_std::prelude::*; +use async_std::sync::{channel, Receiver, Sender}; + +const MAX_JOBS_WAITING: usize = 50; + +use crate::imap::Imap; +use crate::smtp::Smtp; + +/// Job and connection scheduler. +#[derive(Debug)] +pub(crate) enum Scheduler { + Stopped, + Running { + inbox: ImapConnectionState, + mvbox: ImapConnectionState, + sentbox: ImapConnectionState, + smtp: SmtpConnectionState, + }, +} + +impl Scheduler { + /// Start the scheduler, panics if it is already running. + pub async fn run(&mut self) { + match self { + Scheduler::Stopped => { + let ( + ( + ((inbox, inbox_handlers), (mvbox, mvbox_handlers)), + (sentbox, sentbox_handlers), + ), + (smtp, smtp_handlers), + ) = ImapConnectionState::new() + .join(ImapConnectionState::new()) + .join(ImapConnectionState::new()) + .join(SmtpConnectionState::new()) + .await; + + *self = Scheduler::Running { + inbox, + mvbox, + sentbox, + smtp, + }; + } + Scheduler::Running { .. } => { + // TODO: return an error + panic!("WARN: already running"); + } + } + } + + /// Halt the scheduler, panics if it is already stopped. + pub async fn stop(&mut self) { + match self { + Scheduler::Stopped => { + panic!("WARN: already stopped"); + } + Scheduler::Running { + inbox, + mvbox, + sentbox, + smtp, + } => { + inbox + .stop() + .join(mvbox.stop()) + .join(sentbox.stop()) + .join(smtp.stop()) + .await; + } + } + } + + /// Check if the scheduler is running. + pub fn is_running(&self) -> bool { + match self { + Scheduler::Running { .. } => true, + _ => false, + } + } + + /// Check if the scheduler is stoppd. + pub fn is_stopped(&self) -> bool { + match self { + Scheduler::Stopped => true, + _ => false, + } + } +} + +/// Connection state logic shared between imap and smtp connections. +#[derive(Debug)] +struct ConnectionState { + /// Channel to notify that shutdown has completed. + shutdown_receiver: Receiver<()>, + /// Channel to interrupt the whole connection. + stop_sender: Sender<()>, + /// Channel to receive new jobs. + jobs_receiver: Receiver, + /// Channel to schedule new jobs. + jobs_sender: Sender, +} + +impl ConnectionState { + /// Send a new job. + pub async fn send_job(&self, job: T) { + self.jobs_sender.send(job).await; + } + + /// Shutdown this connection completely. + pub async fn stop(&self) { + // Trigger shutdown of the run loop. + self.stop_sender.send(()).await; + // Wait for a notification that the run loop has been shutdown. + self.shutdown_receiver.recv().await; + } +} + +#[derive(Debug)] +pub(crate) struct SmtpConnectionState { + state: ConnectionState, +} + +impl SmtpConnectionState { + async fn new() -> (Self, SmtpConnectionHandlers) { + let (jobs_sender, jobs_receiver) = channel(50); + let (stop_sender, stop_receiver) = channel(1); + let (shutdown_sender, shutdown_receiver) = channel(1); + + let handlers = SmtpConnectionHandlers { + connection: Smtp::new(), + stop_receiver, + shutdown_sender, + }; + + let state = ConnectionState { + shutdown_receiver, + stop_sender, + jobs_sender, + jobs_receiver, + }; + + let conn = SmtpConnectionState { state }; + + (conn, handlers) + } + + /// Send a new job. + async fn send_job(&self, job: SmtpJob) { + self.state.send_job(job).await; + } + + /// Shutdown this connection completely. + async fn stop(&self) { + self.state.stop().await; + } +} + +#[derive(Debug)] +struct SmtpConnectionHandlers { + connection: Smtp, + stop_receiver: Receiver<()>, + shutdown_sender: Sender<()>, +} + +#[derive(Debug)] +pub(crate) struct ImapConnectionState { + /// Channel to interrupt idle. + idle_interrupt_sender: Sender<()>, + state: ConnectionState, +} + +impl ImapConnectionState { + /// Construct a new connection. + async fn new() -> (Self, ImapConnectionHandlers) { + let (jobs_sender, jobs_receiver) = channel(MAX_JOBS_WAITING); + let (stop_sender, stop_receiver) = channel(1); + let (idle_interrupt_sender, idle_interrupt_receiver) = channel(1); + let (shutdown_sender, shutdown_receiver) = channel(1); + + let handlers = ImapConnectionHandlers { + connection: Imap::new(idle_interrupt_receiver), + stop_receiver, + shutdown_sender, + }; + + let state = ConnectionState { + shutdown_receiver, + stop_sender, + jobs_sender, + jobs_receiver, + }; + + let conn = ImapConnectionState { + idle_interrupt_sender, + state, + }; + + (conn, handlers) + } + + /// Send a new job. + async fn send_job(&self, job: T) { + self.state + .send_job(job) + .join(self.idle_interrupt_sender.send(())) + .await; + } + + /// Shutdown this connection completely. + async fn stop(&self) { + self.state.stop().await; + } +} + +#[derive(Debug)] +struct ImapConnectionHandlers { + connection: Imap, + stop_receiver: Receiver<()>, + shutdown_sender: Sender<()>, +} + +/// Jobs handled by the inbox connection. +#[derive(Debug)] +pub enum InboxJob {} + +/// Jobs handled by the mvbox connection. +#[derive(Debug)] +pub enum MvboxJob {} + +/// Jobs handled by the sentbox connection. +#[derive(Debug)] +pub enum SentboxJob {} + +/// Jobs handled by the smtp connection. +#[derive(Debug)] +pub enum SmtpJob {} diff --git a/src/smtp/mod.rs b/src/smtp/mod.rs index 0734c717b..5491c763a 100644 --- a/src/smtp/mod.rs +++ b/src/smtp/mod.rs @@ -50,36 +50,8 @@ impl From for Error { pub type Result = std::result::Result; -#[derive(Debug)] -pub struct Smtp { - inner: RwLock, - pub(crate) state: RwLock, - pub(crate) notify_sender: Sender<()>, - pub(crate) notify_receiver: Receiver<()>, -} - -impl Default for Smtp { - fn default() -> Self { - let (notify_sender, notify_receiver) = channel(1); - Smtp { - inner: Default::default(), - state: Default::default(), - notify_sender, - notify_receiver, - } - } -} - -#[derive(Default, Debug)] -pub struct State { - pub(crate) suspended: bool, - pub(crate) doing_jobs: bool, - pub(crate) perform_jobs_needed: PerformJobsNeeded, - pub(crate) probe_network: bool, -} - #[derive(Default, DebugStub)] -struct SmtpInner { +pub struct Smtp { #[debug_stub(some = "SmtpTransport")] transport: Option, @@ -99,18 +71,17 @@ impl Smtp { } /// Disconnect the SMTP transport and drop it entirely. - pub async fn disconnect(&self) { - let inner = &mut *self.inner.write().await; - if let Some(mut transport) = inner.transport.take() { + pub async fn disconnect(&mut self) { + if let Some(mut transport) = self.transport.take() { transport.close().await.ok(); } - inner.last_success = None; + self.last_success = None; } /// Return true if smtp was connected but is not known to /// have been successfully used the last 60 seconds pub async fn has_maybe_stale_connection(&self) -> bool { - if let Some(last_success) = self.inner.read().await.last_success { + if let Some(last_success) = self.last_success { Instant::now().duration_since(last_success).as_secs() > 60 } else { false @@ -119,17 +90,14 @@ impl Smtp { /// Check whether we are connected. pub async fn is_connected(&self) -> bool { - self.inner - .read() - .await - .transport + self.transport .as_ref() .map(|t| t.is_connected()) .unwrap_or_default() } /// Connect using the provided login params. - pub async fn connect(&self, context: &Context, lp: &LoginParam) -> Result<()> { + pub async fn connect(&mut self, context: &Context, lp: &LoginParam) -> Result<()> { if self.is_connected().await { warn!(context, "SMTP already connected."); return Ok(()); @@ -146,8 +114,7 @@ impl Smtp { error: err, })?; - let inner = &mut *self.inner.write().await; - inner.from = Some(from); + self.from = Some(from); let domain = &lp.send_server; let port = lp.send_port as u16; @@ -208,8 +175,8 @@ impl Smtp { let mut trans = client.into_transport(); trans.connect().await.map_err(Error::ConnectionFailure)?; - inner.transport = Some(trans); - inner.last_success = Some(Instant::now()); + self.transport = Some(trans); + self.last_success = Some(Instant::now()); context.call_cb(Event::SmtpConnected(format!( "SMTP-LOGIN as {} ok", diff --git a/src/smtp/send.rs b/src/smtp/send.rs index 6305acce3..306ba917d 100644 --- a/src/smtp/send.rs +++ b/src/smtp/send.rs @@ -24,7 +24,7 @@ impl Smtp { /// Send a prepared mail to recipients. /// On successful send out Ok() is returned. pub async fn send( - &self, + &mut self, context: &Context, recipients: Vec, message: Vec, @@ -38,23 +38,22 @@ impl Smtp { .collect::>() .join(","); - let envelope = Envelope::new(self.inner.read().await.from.clone(), recipients) - .map_err(Error::EnvelopeError)?; + let envelope = + Envelope::new(self.from.clone(), recipients).map_err(Error::EnvelopeError)?; let mail = SendableEmail::new( envelope, format!("{}", job_id), // only used for internal logging message, ); - let inner = &mut *self.inner.write().await; - if let Some(ref mut transport) = inner.transport { + if let Some(ref mut transport) = self.transport { transport.send(mail).await.map_err(Error::SendError)?; context.call_cb(Event::SmtpMessageSent(format!( "Message len={} was smtp-sent to {}", message_len, recipients_display ))); - inner.last_success = Some(std::time::Instant::now()); + self.last_success = Some(std::time::Instant::now()); Ok(()) } else { From 846ef043d5c9b6dfbb03ee6e0d87456e2b596232 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 18 Mar 2020 00:01:59 +0100 Subject: [PATCH 013/118] hook up scheduler with jobs --- examples/simple.rs | 45 +- src/config.rs | 6 +- src/configure/mod.rs | 4 +- src/context.rs | 4 +- src/imex.rs | 4 +- src/job.rs | 1034 +++++++++++++++++------------------------- src/scheduler.rs | 229 +++++++--- 7 files changed, 604 insertions(+), 722 deletions(-) diff --git a/examples/simple.rs b/examples/simple.rs index c4d96f50f..56b836b03 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -9,10 +9,6 @@ use deltachat::chatlist::*; use deltachat::config; use deltachat::contact::*; use deltachat::context::*; -use deltachat::job::{ - perform_inbox_fetch, perform_inbox_idle, perform_inbox_jobs, perform_smtp_idle, - perform_smtp_jobs, -}; use deltachat::Event; fn cb(_ctx: &Context, event: Event) { @@ -39,38 +35,11 @@ async fn main() { let ctx = Context::new("FakeOs".into(), dbfile.into()) .await .expect("Failed to create context"); - let running = Arc::new(RwLock::new(true)); let info = ctx.get_info().await; let duration = time::Duration::from_millis(4000); println!("info: {:#?}", info); - let ctx = Arc::new(ctx); - let r1 = running.clone(); - - let ctx1 = ctx.clone(); - let t1 = async_std::task::spawn(async move { - while *r1.read().await { - perform_inbox_jobs(&ctx1).await; - if *r1.read().await { - perform_inbox_fetch(&ctx1).await; - - if *r1.read().await { - perform_inbox_idle(&ctx1).await; - } - } - } - }); - - let r1 = running.clone(); - let ctx1 = ctx.clone(); - let t2 = async_std::task::spawn(async move { - while *r1.read().await { - perform_smtp_jobs(&ctx1).await; - if *r1.read().await { - perform_smtp_idle(&ctx1).await; - } - } - }); + ctx.run().await; println!("configuring"); let args = std::env::args().collect::>(); @@ -107,15 +76,7 @@ async fn main() { async_std::task::sleep(duration).await; - println!("stopping threads"); - - *running.write().await = false; - deltachat::job::interrupt_inbox_idle(&ctx).await; - deltachat::job::interrupt_smtp_idle(&ctx).await; - - println!("joining"); - t1.await; - t2.await; - + println!("stopping"); + ctx.stop().await; println!("closing"); } diff --git a/src/config.rs b/src/config.rs index 66197c970..76832d014 100644 --- a/src/config.rs +++ b/src/config.rs @@ -152,17 +152,17 @@ impl Context { } Config::InboxWatch => { let ret = self.sql.set_raw_config(self, key, value).await; - interrupt_inbox_idle(self).await; + self.interrupt_inbox().await; ret } Config::SentboxWatch => { let ret = self.sql.set_raw_config(self, key, value).await; - interrupt_sentbox_idle(self).await; + self.interrupt_sentbox().await; ret } Config::MvboxWatch => { let ret = self.sql.set_raw_config(self, key, value).await; - interrupt_mvbox_idle(self).await; + self.interrupt_mvbox().await; ret } Config::Selfstatus => { diff --git a/src/configure/mod.rs b/src/configure/mod.rs index 726883e66..1fcf132b2 100644 --- a/src/configure/mod.rs +++ b/src/configure/mod.rs @@ -37,8 +37,8 @@ impl Context { warn!(self, "There is already another ongoing process running.",); return; } - job::kill_action(self, job::Action::ConfigureImap).await; - job::add(self, job::Action::ConfigureImap, 0, Params::new(), 0).await; + // job::kill_action(self, job::Action::ConfigureImap).await; + // job::add(self, job::Action::ConfigureImap, 0, Params::new(), 0).await; } /// Checks if the context is already configured. diff --git a/src/context.rs b/src/context.rs index ef3a77230..6499c2a26 100644 --- a/src/context.rs +++ b/src/context.rs @@ -26,7 +26,7 @@ use crate::scheduler::Scheduler; use crate::smtp::Smtp; use crate::sql::Sql; -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Context { pub(crate) inner: Arc, } @@ -131,7 +131,7 @@ impl Context { } pub async fn run(&self) { - self.inner.scheduler.write().await.run().await + self.inner.scheduler.write().await.run(self.clone()).await } pub async fn stop(&self) { diff --git a/src/imex.rs b/src/imex.rs index ae5589321..209ea856b 100644 --- a/src/imex.rs +++ b/src/imex.rs @@ -77,8 +77,8 @@ pub async fn imex(context: &Context, what: ImexMode, param1: Option for Thread { MarkseenMdnOnImap => Thread::Imap, MarkseenMsgOnImap => Thread::Imap, MoveMsg => Thread::Imap, - ConfigureImap => Thread::Imap, - ImexImap => Thread::Imap, MaybeSendLocations => Thread::Smtp, MaybeSendLocationsEnded => Thread::Smtp, @@ -179,132 +176,127 @@ impl Job { recipients: Vec, message: Vec, job_id: u32, + smtp: &mut Smtp, success_cb: F, ) -> Status where F: FnOnce() -> Fut, Fut: Future>, { - unimplemented!(); - // // hold the smtp lock during sending of a job and - // // its ok/error response processing. Note that if a message - // // was sent we need to mark it in the database ASAP as we - // // otherwise might send it twice. - // if std::env::var(crate::DCC_MIME_DEBUG).is_ok() { - // info!(context, "smtp-sending out mime message:"); - // println!("{}", String::from_utf8_lossy(&message)); - // } - // match context - // .smtp - // .send(context, recipients, message, job_id) - // .await - // { - // Err(crate::smtp::send::Error::SendError(err)) => { - // // Remote error, retry later. - // warn!(context, "SMTP failed to send: {}", err); - // self.pending_error = Some(err.to_string()); + // hold the smtp lock during sending of a job and + // its ok/error response processing. Note that if a message + // was sent we need to mark it in the database ASAP as we + // otherwise might send it twice. + if std::env::var(crate::DCC_MIME_DEBUG).is_ok() { + info!(context, "smtp-sending out mime message:"); + println!("{}", String::from_utf8_lossy(&message)); + } + match smtp.send(context, recipients, message, job_id).await { + Err(crate::smtp::send::Error::SendError(err)) => { + // Remote error, retry later. + warn!(context, "SMTP failed to send: {}", err); + self.pending_error = Some(err.to_string()); - // let res = match err { - // async_smtp::smtp::error::Error::Permanent(_) => { - // Status::Finished(Err(format_err!("Permanent SMTP error: {}", err))) - // } - // async_smtp::smtp::error::Error::Transient(_) => { - // // We got a transient 4xx response from SMTP server. - // // Give some time until the server-side error maybe goes away. - // Status::RetryLater - // } - // _ => { - // if context.smtp.has_maybe_stale_connection().await { - // info!(context, "stale connection? immediately reconnecting"); - // Status::RetryNow - // } else { - // Status::RetryLater - // } - // } - // }; + let res = match err { + async_smtp::smtp::error::Error::Permanent(_) => { + Status::Finished(Err(format_err!("Permanent SMTP error: {}", err))) + } + async_smtp::smtp::error::Error::Transient(_) => { + // We got a transient 4xx response from SMTP server. + // Give some time until the server-side error maybe goes away. + Status::RetryLater + } + _ => { + if smtp.has_maybe_stale_connection().await { + info!(context, "stale connection? immediately reconnecting"); + Status::RetryNow + } else { + Status::RetryLater + } + } + }; - // // this clears last_success info - // context.smtp.disconnect().await; + // this clears last_success info + smtp.disconnect().await; - // res - // } - // Err(crate::smtp::send::Error::EnvelopeError(err)) => { - // // Local error, job is invalid, do not retry. - // context.smtp.disconnect().await; - // warn!(context, "SMTP job is invalid: {}", err); - // Status::Finished(Err(Error::SmtpError(err))) - // } - // Err(crate::smtp::send::Error::NoTransport) => { - // // Should never happen. - // // It does not even make sense to disconnect here. - // error!(context, "SMTP job failed because SMTP has no transport"); - // Status::Finished(Err(format_err!("SMTP has not transport"))) - // } - // Ok(()) => { - // job_try!(success_cb().await); - // Status::Finished(Ok(())) - // } - // } + res + } + Err(crate::smtp::send::Error::EnvelopeError(err)) => { + // Local error, job is invalid, do not retry. + smtp.disconnect().await; + warn!(context, "SMTP job is invalid: {}", err); + Status::Finished(Err(Error::SmtpError(err))) + } + Err(crate::smtp::send::Error::NoTransport) => { + // Should never happen. + // It does not even make sense to disconnect here. + error!(context, "SMTP job failed because SMTP has no transport"); + Status::Finished(Err(format_err!("SMTP has not transport"))) + } + Ok(()) => { + job_try!(success_cb().await); + Status::Finished(Ok(())) + } + } } - async fn send_msg_to_smtp(&mut self, context: &Context) -> Status { - unimplemented!(); - // connect to// SMTP server, if not yet done - // if !context.smtp.is_connected().await { - // let loginparam = LoginParam::from_database(context, "configured_").await; - // if let Err(err) = context.smtp.connect(context, &loginparam).await { - // warn!(context, "SMTP connection failure: {:?}", err); - // return Status::RetryLater; - // } - // } + async fn send_msg_to_smtp(&mut self, context: &Context, smtp: &mut Smtp) -> Status { + // SMTP server, if not yet done + if !smtp.is_connected().await { + let loginparam = LoginParam::from_database(context, "configured_").await; + if let Err(err) = smtp.connect(context, &loginparam).await { + warn!(context, "SMTP connection failure: {:?}", err); + return Status::RetryLater; + } + } - // 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"))); - // 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 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"))); + 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_list = recipients - // .split('\x1e') - // .filter_map( - // |addr| match async_smtp::EmailAddress::new(addr.to_string()) { - // Ok(addr) => Some(addr), - // Err(err) => { - // warn!(context, "invalid recipient: {} {:?}", addr, err); - // None - // } - // }, - // ) - // .collect::>(); + let recipients_list = recipients + .split('\x1e') + .filter_map( + |addr| match async_smtp::EmailAddress::new(addr.to_string()) { + Ok(addr) => Some(addr), + Err(err) => { + warn!(context, "invalid recipient: {} {:?}", addr, err); + None + } + }, + ) + .collect::>(); - // /* if there is a msg-id and it does not exist in the db, cancel sending. - // this happends if dc_delete_msgs() was called - // before the generated mime was sent out */ - // if 0 != self.foreign_id && !message::exists(context, MsgId::new(self.foreign_id)).await { - // return Status::Finished(Err(format_err!( - // "Not sending Message {} as it was deleted", - // self.foreign_id - // ))); - // }; + /* if there is a msg-id and it does not exist in the db, cancel sending. + this happends if dc_delete_msgs() was called + before the generated mime was sent out */ + if 0 != self.foreign_id && !message::exists(context, MsgId::new(self.foreign_id)).await { + return Status::Finished(Err(format_err!( + "Not sending Message {} as it was deleted", + self.foreign_id + ))); + }; - // let foreign_id = self.foreign_id; - // self.smtp_send(context, recipients_list, body, self.job_id, || { - // async move { - // // smtp success, update db ASAP, then delete smtp file - // if 0 != foreign_id { - // set_delivered(context, MsgId::new(foreign_id)).await; - // } - // // now also delete the generated file - // dc_delete_file(context, filename).await; - // Ok(()) - // } - // }) - // .await + let foreign_id = self.foreign_id; + self.smtp_send(context, recipients_list, body, self.job_id, smtp, || { + async move { + // smtp success, update db ASAP, then delete smtp file + if 0 != foreign_id { + set_delivered(context, MsgId::new(foreign_id)).await; + } + // now also delete the generated file + dc_delete_file(context, filename).await; + Ok(()) + } + }) + .await } /// Get `SendMdn` jobs with foreign_id equal to `contact_id` excluding the `job_id` job. @@ -350,235 +342,223 @@ impl Job { Ok((job_ids, rfc724_mids)) } - async fn send_mdn(&mut self, context: &Context) -> Status { - unimplemented!(); - // if !context.get_config_bool(Config::MdnsEnabled).await { - // // User has disabled MDNs after job scheduling but before - // // execution. - // return Status::Finished(Err(format_err!("MDNs are disabled"))); - // } + async fn send_mdn(&mut self, context: &Context, smtp: &mut Smtp) -> Status { + if !context.get_config_bool(Config::MdnsEnabled).await { + // User has disabled MDNs after job scheduling but before + // execution. + return Status::Finished(Err(format_err!("MDNs are disabled"))); + } - // let contact_id = self.foreign_id; - // let contact = job_try!(Contact::load_from_db(context, contact_id).await); - // if contact.is_blocked() { - // return Status::Finished(Err(format_err!("Contact is blocked"))); - // } + let contact_id = self.foreign_id; + let contact = job_try!(Contact::load_from_db(context, contact_id).await); + if contact.is_blocked() { + return Status::Finished(Err(format_err!("Contact is blocked"))); + } - // let msg_id = if let Some(msg_id) = self.param.get_msg_id() { - // msg_id - // } else { - // return Status::Finished(Err(format_err!( - // "SendMdn job has invalid parameters: {}", - // self.param - // ))); - // }; + let msg_id = if let Some(msg_id) = self.param.get_msg_id() { + msg_id + } else { + return Status::Finished(Err(format_err!( + "SendMdn job has invalid parameters: {}", + self.param + ))); + }; - // // Try to aggregate other SendMdn jobs and send a combined MDN. - // let (additional_job_ids, additional_rfc724_mids) = self - // .get_additional_mdn_jobs(context, contact_id) - // .await - // .unwrap_or_default(); + // Try to aggregate other SendMdn jobs and send a combined MDN. + let (additional_job_ids, additional_rfc724_mids) = self + .get_additional_mdn_jobs(context, contact_id) + .await + .unwrap_or_default(); - // if !additional_rfc724_mids.is_empty() { - // info!( - // context, - // "SendMdn job: aggregating {} additional MDNs", - // additional_rfc724_mids.len() - // ) - // } + if !additional_rfc724_mids.is_empty() { + info!( + context, + "SendMdn job: aggregating {} additional MDNs", + additional_rfc724_mids.len() + ) + } - // let msg = job_try!(Message::load_from_db(context, msg_id).await); - // let mimefactory = - // job_try!(MimeFactory::from_mdn(context, &msg, additional_rfc724_mids).await); - // let rendered_msg = job_try!(mimefactory.render().await); - // let body = rendered_msg.message; + let msg = job_try!(Message::load_from_db(context, msg_id).await); + let mimefactory = + job_try!(MimeFactory::from_mdn(context, &msg, additional_rfc724_mids).await); + let rendered_msg = job_try!(mimefactory.render().await); + let body = rendered_msg.message; - // let addr = contact.get_addr(); - // let recipient = job_try!(async_smtp::EmailAddress::new(addr.to_string()) - // .map_err(|err| format_err!("invalid recipient: {} {:?}", addr, err))); - // let recipients = vec![recipient]; + let addr = contact.get_addr(); + let recipient = job_try!(async_smtp::EmailAddress::new(addr.to_string()) + .map_err(|err| format_err!("invalid recipient: {} {:?}", addr, err))); + let recipients = vec![recipient]; - // // connect to SMTP server, if not yet done - // if !context.smtp.is_connected().await { - // let loginparam = LoginParam::from_database(context, "configured_").await; - // if let Err(err) = context.smtp.connect(context, &loginparam).await { - // warn!(context, "SMTP connection failure: {:?}", err); - // return Status::RetryLater; - // } - // } + // connect to SMTP server, if not yet done + if !smtp.is_connected().await { + let loginparam = LoginParam::from_database(context, "configured_").await; + if let Err(err) = smtp.connect(context, &loginparam).await { + warn!(context, "SMTP connection failure: {:?}", err); + return Status::RetryLater; + } + } - // self.smtp_send(context, recipients, body, self.job_id, || { - // async move { - // // Remove additional SendMdn jobs we have aggregated into this one. - // job::kill_ids(context, &additional_job_ids).await?; - // Ok(()) - // } - // }) - // .await + self.smtp_send(context, recipients, body, self.job_id, smtp, || { + async move { + // Remove additional SendMdn jobs we have aggregated into this one. + kill_ids(context, &additional_job_ids).await?; + Ok(()) + } + }) + .await } - async fn move_msg(&mut self, context: &Context) -> Status { - unimplemented!(); - // let imap_inbox = &context.inbox_thread.imap; + async fn move_msg(&mut self, context: &Context, imap: &mut Imap) -> Status { + let msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)).await); - // let msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)).await); + if let Err(err) = imap.ensure_configured_folders(context, true).await { + warn!(context, "could not configure folders: {:?}", err); + return Status::RetryLater; + } + let dest_folder = context + .sql + .get_raw_config(context, "configured_mvbox_folder") + .await; - // if let Err(err) = imap_inbox.ensure_configured_folders(context, true).await { - // warn!(context, "could not configure folders: {:?}", err); - // return Status::RetryLater; - // } - // let dest_folder = context - // .sql - // .get_raw_config(context, "configured_mvbox_folder") - // .await; + if let Some(dest_folder) = dest_folder { + let server_folder = msg.server_folder.as_ref().unwrap(); + let mut dest_uid = 0; - // if let Some(dest_folder) = dest_folder { - // let server_folder = msg.server_folder.as_ref().unwrap(); - // let mut dest_uid = 0; - - // match imap_inbox - // .mv( - // context, - // server_folder, - // msg.server_uid, - // &dest_folder, - // &mut dest_uid, - // ) - // .await - // { - // ImapActionResult::RetryLater => Status::RetryLater, - // ImapActionResult::Success => { - // message::update_server_uid(context, &msg.rfc724_mid, &dest_folder, dest_uid) - // .await; - // Status::Finished(Ok(())) - // } - // ImapActionResult::Failed => { - // Status::Finished(Err(format_err!("IMAP action failed"))) - // } - // ImapActionResult::AlreadyDone => Status::Finished(Ok(())), - // } - // } else { - // Status::Finished(Err(format_err!("No mvbox folder configured"))) - // } + match imap + .mv( + context, + server_folder, + msg.server_uid, + &dest_folder, + &mut dest_uid, + ) + .await + { + ImapActionResult::RetryLater => Status::RetryLater, + ImapActionResult::Success => { + message::update_server_uid(context, &msg.rfc724_mid, &dest_folder, dest_uid) + .await; + Status::Finished(Ok(())) + } + ImapActionResult::Failed => { + Status::Finished(Err(format_err!("IMAP action failed"))) + } + ImapActionResult::AlreadyDone => Status::Finished(Ok(())), + } + } else { + Status::Finished(Err(format_err!("No mvbox folder configured"))) + } } - async fn delete_msg_on_imap(&mut self, context: &Context) -> Status { - unimplemented!(); - // let imap_inbox = &context.inbox_thread.imap; + async fn delete_msg_on_imap(&mut self, context: &Context, imap: &mut Imap) -> Status { + let mut msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)).await); - // let mut msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)).await); - - // if !msg.rfc724_mid.is_empty() { - // if message::rfc724_mid_cnt(context, &msg.rfc724_mid).await > 1 { - // info!( - // context, - // "The message is deleted from the server when all parts are deleted.", - // ); - // } else { - // /* if this is the last existing part of the message, - // we delete the message from the server */ - // let mid = msg.rfc724_mid; - // let server_folder = msg.server_folder.as_ref().unwrap(); - // let res = imap_inbox - // .delete_msg(context, &mid, server_folder, &mut msg.server_uid) - // .await; - // if res == ImapActionResult::RetryLater { - // // XXX RetryLater is converted to RetryNow here - // return Status::RetryNow; - // } - // } - // Message::delete_from_db(context, msg.id).await; - // Status::Finished(Ok(())) - // } else { - // /* eg. device messages have no Message-ID */ - // Status::Finished(Ok(())) - // } + if !msg.rfc724_mid.is_empty() { + if message::rfc724_mid_cnt(context, &msg.rfc724_mid).await > 1 { + info!( + context, + "The message is deleted from the server when all parts are deleted.", + ); + } else { + /* if this is the last existing part of the message, + we delete the message from the server */ + let mid = msg.rfc724_mid; + let server_folder = msg.server_folder.as_ref().unwrap(); + let res = imap + .delete_msg(context, &mid, server_folder, &mut msg.server_uid) + .await; + if res == ImapActionResult::RetryLater { + // XXX RetryLater is converted to RetryNow here + return Status::RetryNow; + } + } + Message::delete_from_db(context, msg.id).await; + Status::Finished(Ok(())) + } else { + /* eg. device messages have no Message-ID */ + Status::Finished(Ok(())) + } } - async fn empty_server(&mut self, context: &Context) -> Status { - unimplemented!(); - // let imap_inbox = &context.inbox_thread.imap; - // if self.foreign_id & DC_EMPTY_MVBOX > 0 { - // if let Some(mvbox_folder) = context - // .sql - // .get_raw_config(context, "configured_mvbox_folder") - // .await - // { - // imap_inbox.empty_folder(context, &mvbox_folder).await; - // } - // } - // if self.foreign_id & DC_EMPTY_INBOX > 0 { - // imap_inbox.empty_folder(context, "INBOX").await; - // } - // Status::Finished(Ok(())) + async fn empty_server(&mut self, context: &Context, imap: &mut Imap) -> Status { + if self.foreign_id & DC_EMPTY_MVBOX > 0 { + if let Some(mvbox_folder) = context + .sql + .get_raw_config(context, "configured_mvbox_folder") + .await + { + imap.empty_folder(context, &mvbox_folder).await; + } + } + if self.foreign_id & DC_EMPTY_INBOX > 0 { + imap.empty_folder(context, "INBOX").await; + } + Status::Finished(Ok(())) } - async fn markseen_msg_on_imap(&mut self, context: &Context) -> Status { - unimplemented!(); - // let imap_inbox = &context.inbox_thread.imap; + async fn markseen_msg_on_imap(&mut self, context: &Context, imap: &mut Imap) -> Status { + let msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)).await); - // let msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)).await); - - // let folder = msg.server_folder.as_ref().unwrap(); - // match imap_inbox.set_seen(context, folder, msg.server_uid).await { - // ImapActionResult::RetryLater => Status::RetryLater, - // ImapActionResult::AlreadyDone => Status::Finished(Ok(())), - // ImapActionResult::Success | ImapActionResult::Failed => { - // // XXX the message might just have been moved - // // we want to send out an MDN anyway - // // The job will not be retried so locally - // // there is no risk of double-sending MDNs. - // if msg.param.get_bool(Param::WantsMdn).unwrap_or_default() - // && context.get_config_bool(Config::MdnsEnabled).await - // { - // if let Err(err) = send_mdn(context, &msg).await { - // warn!(context, "could not send out mdn for {}: {}", msg.id, err); - // return Status::Finished(Err(err)); - // } - // } - // Status::Finished(Ok(())) - // } - // } + let folder = msg.server_folder.as_ref().unwrap(); + match imap.set_seen(context, folder, msg.server_uid).await { + ImapActionResult::RetryLater => Status::RetryLater, + ImapActionResult::AlreadyDone => Status::Finished(Ok(())), + ImapActionResult::Success | ImapActionResult::Failed => { + // XXX the message might just have been moved + // we want to send out an MDN anyway + // The job will not be retried so locally + // there is no risk of double-sending MDNs. + if msg.param.get_bool(Param::WantsMdn).unwrap_or_default() + && context.get_config_bool(Config::MdnsEnabled).await + { + if let Err(err) = send_mdn(context, &msg).await { + warn!(context, "could not send out mdn for {}: {}", msg.id, err); + return Status::Finished(Err(err)); + } + } + Status::Finished(Ok(())) + } + } } - async fn markseen_mdn_on_imap(&mut self, context: &Context) -> Status { - unimplemented!(); - // let folder = self - // .param - // .get(Param::ServerFolder) - // .unwrap_or_default() - // .to_string(); - // let uid = self.param.get_int(Param::ServerUid).unwrap_or_default() as u32; - // let imap_inbox = &context.inbox_thread.imap; - // if imap_inbox.set_seen(context, &folder, uid).await == ImapActionResult::RetryLater { - // return Status::RetryLater; - // } - // if self.param.get_bool(Param::AlsoMove).unwrap_or_default() { - // if let Err(err) = imap_inbox.ensure_configured_folders(context, true).await { - // warn!(context, "configuring folders failed: {:?}", err); - // return Status::RetryLater; - // } - // let dest_folder = context - // .sql - // .get_raw_config(context, "configured_mvbox_folder") - // .await; - // if let Some(dest_folder) = dest_folder { - // let mut dest_uid = 0; - // if ImapActionResult::RetryLater - // == imap_inbox - // .mv(context, &folder, uid, &dest_folder, &mut dest_uid) - // .await - // { - // Status::RetryLater - // } else { - // Status::Finished(Ok(())) - // } - // } else { - // Status::Finished(Err(format_err!("MVBOX is not configured"))) - // } - // } else { - // Status::Finished(Ok(())) - // } + async fn markseen_mdn_on_imap(&mut self, context: &Context, imap: &mut Imap) -> Status { + let folder = self + .param + .get(Param::ServerFolder) + .unwrap_or_default() + .to_string(); + let uid = self.param.get_int(Param::ServerUid).unwrap_or_default() as u32; + + if imap.set_seen(context, &folder, uid).await == ImapActionResult::RetryLater { + return Status::RetryLater; + } + + if self.param.get_bool(Param::AlsoMove).unwrap_or_default() { + if let Err(err) = imap.ensure_configured_folders(context, true).await { + warn!(context, "configuring folders failed: {:?}", err); + return Status::RetryLater; + } + let dest_folder = context + .sql + .get_raw_config(context, "configured_mvbox_folder") + .await; + if let Some(dest_folder) = dest_folder { + let mut dest_uid = 0; + if ImapActionResult::RetryLater + == imap + .mv(context, &folder, uid, &dest_folder, &mut dest_uid) + .await + { + Status::RetryLater + } else { + Status::Finished(Ok(())) + } + } else { + Status::Finished(Err(format_err!("MVBOX is not configured"))) + } + } else { + Status::Finished(Ok(())) + } } } @@ -592,7 +572,7 @@ pub async fn kill_action(context: &Context, action: Action) -> bool { } /// Remove jobs with specified IDs. -pub async fn kill_ids(context: &Context, job_ids: &[u32]) -> sql::Result<()> { +async fn kill_ids(context: &Context, job_ids: &[u32]) -> sql::Result<()> { context .sql .execute( @@ -606,129 +586,6 @@ pub async fn kill_ids(context: &Context, job_ids: &[u32]) -> sql::Result<()> { Ok(()) } -pub async fn perform_inbox_fetch(context: &Context) { - unimplemented!(); - // let use_network = context.get_config_bool(Config::InboxWatch).await; - - // context.inbox_thread.fetch(context, use_network).await; -} - -pub async fn perform_mvbox_fetch(context: &Context) { - unimplemented!(); - // let use_network = context.get_config_bool(Config::MvboxWatch).await; - - // context.mvbox_thread.fetch(context, use_network).await; -} - -pub async fn perform_sentbox_fetch(context: &Context) { - unimplemented!(); - // let use_network = context.get_config_bool(Config::SentboxWatch).await; - - // context.sentbox_thread.fetch(context, use_network).await; -} - -pub async fn perform_inbox_idle(context: &Context) { - unimplemented!(); - // if context - // .perform_inbox_jobs_needed - // .load(std::sync::atomic::Ordering::Relaxed) - // { - // info!( - // context, - // "INBOX-IDLE will not be started because of waiting jobs." - // ); - // return; - // } - // let use_network = context.get_config_bool(Config::InboxWatch).await; - - // context.inbox_thread.idle(context, use_network).await; -} - -pub async fn perform_mvbox_idle(context: &Context) { - unimplemented!(); - // let use_network = context.get_config_bool(Config::MvboxWatch).await; - - // context.mvbox_thread.idle(context, use_network).await; - // } - - // pub async fn perform_sentbox_idle(context: &Context) { - // let use_network = context.get_config_bool(Config::SentboxWatch).await; - - // context.sentbox_thread.idle(context, use_network).await; -} - -pub async fn interrupt_inbox_idle(context: &Context) { - unimplemented!(); - // info!(context, "interrupt_inbox_idle called"); - // // we do not block on trying to obtain the thread lock - // // because we don't know in which state the thread is. - // // If it's currently fetching then we can not get the lock - // // but we flag it for checking jobs so that idle will be skipped. - // if !context.inbox_thread.try_interrupt_idle(context).await { - // context - // .perform_inbox_jobs_needed - // .store(true, std::sync::atomic::Ordering::Relaxed); - // warn!(context, "could not interrupt idle"); - // } -} - -pub async fn interrupt_mvbox_idle(context: &Context) { - unimplemented!(); - // context.mvbox_thread.interrupt_idle(context).await; -} - -pub async fn interrupt_sentbox_idle(context: &Context) { - unimplemented!(); - // context.sentbox_thread.interrupt_idle(context).await; -} - -pub async fn perform_smtp_jobs(context: &Context) { - unimplemented!(); - // let probe_smtp_network = { - // let state = &mut *context.smtp.state.write().await; - - // let probe_smtp_network = state.probe_network; - // state.probe_network = false; - // state.perform_jobs_needed = PerformJobsNeeded::Not; - - // if state.suspended { - // info!(context, "SMTP-jobs suspended.",); - // return; - // } - // state.doing_jobs = true; - // probe_smtp_network - // }; - - // info!(context, "SMTP-jobs started...",); - // job_perform(context, Thread::Smtp, probe_smtp_network).await; - // info!(context, "SMTP-jobs ended."); - - // context.smtp.state.write().await.doing_jobs = false; -} - -pub async fn perform_smtp_idle(context: &Context) { - unimplemented!(); - // info!(context, "SMTP-idle started..."); - - // let perform_jobs_needed = context.smtp.state.read().await.perform_jobs_needed.clone(); - - // match perform_jobs_needed { - // PerformJobsNeeded::AtOnce => { - // info!( - // context, - // "SMTP-idle will not be started because of waiting jobs.", - // ); - // } - // PerformJobsNeeded::Not | PerformJobsNeeded::AvoidDos => { - // let dur = get_next_wakeup_time(context, Thread::Smtp).await; - - // context.smtp.notify_receiver.recv().timeout(dur).await.ok(); - // } - // } - - // info!(context, "SMTP-idle ended.",); -} - async fn get_next_wakeup_time(context: &Context, thread: Thread) -> time::Duration { let t: i64 = context .sql @@ -897,162 +754,122 @@ pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> { Ok(()) } -pub async fn perform_inbox_jobs(context: &Context) { - unimplemented!(); - // info!(context, "dc_perform_inbox_jobs starting.",); - - // let probe_imap_network = context - // .probe_imap_network - // .load(std::sync::atomic::Ordering::Relaxed); - // context - // .probe_imap_network - // .store(false, std::sync::atomic::Ordering::Relaxed); - // context - // .perform_inbox_jobs_needed - // .store(false, std::sync::atomic::Ordering::Relaxed); - - // job_perform(context, Thread::Imap, probe_imap_network).await; - // info!(context, "dc_perform_inbox_jobs ended.",); +#[derive(Debug)] +pub enum Connection<'a> { + Inbox(&'a mut Imap), + Smtp(&'a mut Smtp), } -pub async fn perform_mvbox_jobs(context: &Context) { - unimplemented!(); - // info!(context, "dc_perform_mbox_jobs EMPTY (for now)."); +impl<'a> fmt::Display for Connection<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Connection::Inbox(_) => write!(f, "Inbox"), + Connection::Smtp(_) => write!(f, "Smtp"), + } + } } -pub async fn perform_sentbox_jobs(context: &Context) { - unimplemented!(); - // info!(context, "dc_perform_sentbox_jobs EMPTY (for now)."); +impl<'a> Connection<'a> { + fn inbox(&mut self) -> &mut Imap { + match self { + Connection::Inbox(imap) => imap, + _ => panic!("Not an inbox"), + } + } + + fn smtp(&mut self) -> &mut Smtp { + match self { + Connection::Smtp(smtp) => smtp, + _ => panic!("Not a smtp"), + } + } } -async fn job_perform(context: &Context, thread: Thread, probe_network: bool) { - unimplemented!(); - // while let Some(mut job) = load_next_job(context, thread, probe_network).await { - // info!(context, "{}-job {} started...", thread, job); +pub(crate) async fn perform_job(context: &Context, mut connection: Connection<'_>, mut job: Job) { + info!(context, "{}-job {} started...", &connection, &job); - // // some configuration jobs are "exclusive": - // // - they are always executed in the imap-thread and the smtp-thread is suspended during execution - // // - they may change the database handle; we do not keep old pointers therefore - // // - they can be re-executed one time AT_ONCE, but they are not saved in the database for later execution - // if Action::ConfigureImap == job.action || Action::ImexImap == job.action { - // job::kill_action(context, job.action).await; - // context.sentbox_thread.suspend(context).await; - // context.mvbox_thread.suspend(context).await; - // suspend_smtp_thread(context, true).await; - // } + let try_res = match perform_job_action(context, &mut job, &mut connection, 0).await { + Status::RetryNow => perform_job_action(context, &mut job, &mut connection, 1).await, + x => x, + }; - // let try_res = match perform_job_action(context, &mut job, thread, 0).await { - // Status::RetryNow => perform_job_action(context, &mut job, thread, 1).await, - // x => x, - // }; + match try_res { + Status::RetryNow | Status::RetryLater => { + let tries = job.tries + 1; - // if Action::ConfigureImap == job.action || Action::ImexImap == job.action { - // context.sentbox_thread.unsuspend(context).await; - // context.mvbox_thread.unsuspend(context).await; - // suspend_smtp_thread(context, false).await; - // break; - // } + if tries < JOB_RETRIES { + info!( + context, + "{} thread increases job {} tries to {}", &connection, job, tries + ); + job.tries = tries; + let time_offset = get_backoff_time_offset(tries); + job.desired_timestamp = time() + time_offset; + job.update(context).await; + info!( + context, + "{}-job #{} not succeeded on try #{}, retry in {} seconds.", + &connection, + job.job_id as u32, + tries, + time_offset + ); + } else { + info!( + context, + "{} thread removes job {} as it exhausted {} retries", + &connection, + job, + JOB_RETRIES + ); + job.delete(context).await; + } + } + Status::Finished(res) => { + if let Err(err) = res { + warn!( + context, + "{} removes job {} as it failed with error {:?}", &connection, job, err + ); + } else { + info!( + context, + "{} removes job {} as it succeeded", &connection, job + ); + } - // match try_res { - // Status::RetryNow | Status::RetryLater => { - // let tries = job.tries + 1; - - // if tries < JOB_RETRIES { - // info!( - // context, - // "{} thread increases job {} tries to {}", thread, job, tries - // ); - // job.tries = tries; - // let time_offset = get_backoff_time_offset(tries); - // job.desired_timestamp = time() + time_offset; - // job.update(context).await; - // info!( - // context, - // "{}-job #{} not succeeded on try #{}, retry in {} seconds.", - // thread, - // job.job_id as u32, - // tries, - // time_offset - // ); - // if thread == Thread::Smtp && tries < JOB_RETRIES - 1 { - // context.smtp.state.write().await.perform_jobs_needed = - // PerformJobsNeeded::AvoidDos; - // } - // } else { - // info!( - // context, - // "{} thread removes job {} as it exhausted {} retries", - // thread, - // job, - // JOB_RETRIES - // ); - // if job.action == Action::SendMsgToSmtp { - // message::set_msg_failed( - // context, - // MsgId::new(job.foreign_id), - // job.pending_error.as_ref(), - // ) - // .await; - // } - // job.delete(context).await; - // } - // if !probe_network { - // continue; - // } - // // on dc_maybe_network() we stop trying here; - // // these jobs are already tried once. - // // otherwise, we just continue with the next job - // // to give other jobs a chance being tried at least once. - // break; - // } - // Status::Finished(res) => { - // if let Err(err) = res { - // warn!( - // context, - // "{} removes job {} as it failed with error {:?}", thread, job, err - // ); - // } else { - // info!(context, "{} removes job {} as it succeeded", thread, job); - // } - - // job.delete(context).await; - // } - // } - // } + job.delete(context).await; + } + } } async fn perform_job_action( context: &Context, - mut job: &mut Job, - thread: Thread, + job: &mut Job, + connection: &mut Connection<'_>, tries: u32, ) -> Status { info!( context, - "{} begin immediate try {} of job {}", thread, tries, job + "{} begin immediate try {} of job {}", &connection, tries, job ); let try_res = match job.action { - Action::Unknown => Status::Finished(Err(format_err!("Unknown job id found"))), - Action::SendMsgToSmtp => job.send_msg_to_smtp(context).await, - Action::EmptyServer => job.empty_server(context).await, - Action::DeleteMsgOnImap => job.delete_msg_on_imap(context).await, - Action::MarkseenMsgOnImap => job.markseen_msg_on_imap(context).await, - Action::MarkseenMdnOnImap => job.markseen_mdn_on_imap(context).await, - Action::MoveMsg => job.move_msg(context).await, - Action::SendMdn => job.send_mdn(context).await, - Action::ConfigureImap => job_configure_imap(context).await, - Action::ImexImap => match job_imex_imap(context, &job).await { - Ok(()) => Status::Finished(Ok(())), - Err(err) => { - error!(context, "{}", err); - Status::Finished(Err(err)) - } - }, - Action::MaybeSendLocations => location::job_maybe_send_locations(context, &job).await, - Action::MaybeSendLocationsEnded => { - location::job_maybe_send_locations_ended(context, &mut job).await + Action::Unknown => { + warn!(context, "ignoring unknown job"); + Status::Finished(Ok(())) } + Action::SendMsgToSmtp => job.send_msg_to_smtp(context, connection.smtp()).await, + Action::SendMdn => job.send_mdn(context, connection.smtp()).await, + Action::MaybeSendLocations => location::job_maybe_send_locations(context, job).await, + Action::MaybeSendLocationsEnded => { + location::job_maybe_send_locations_ended(context, job).await + } + Action::EmptyServer => job.empty_server(context, connection.inbox()).await, + Action::DeleteMsgOnImap => job.delete_msg_on_imap(context, connection.inbox()).await, + Action::MarkseenMsgOnImap => job.markseen_msg_on_imap(context, connection.inbox()).await, + Action::MarkseenMdnOnImap => job.markseen_mdn_on_imap(context, connection.inbox()).await, + Action::MoveMsg => job.move_msg(context, connection.inbox()).await, Action::Housekeeping => { sql::housekeeping(context).await; Status::Finished(Ok(())) @@ -1061,7 +878,7 @@ async fn perform_job_action( info!( context, - "{} finished immediate try {} of job {}", thread, tries, job + "Inbox finished immediate try {} of job {}", tries, job ); try_res @@ -1078,24 +895,11 @@ fn get_backoff_time_offset(tries: u32) -> i64 { seconds as i64 } -async fn suspend_smtp_thread(context: &Context, suspend: bool) { - unimplemented!(); - // context.smtp.state.write().await.suspended = suspend; - // if suspend { - // loop { - // if !context.smtp.state.read().await.doing_jobs { - // return; - // } - // async_std::task::sleep(time::Duration::from_micros(300 * 1000)).await; - // } - // } -} - async fn send_mdn(context: &Context, msg: &Message) -> Result<()> { let mut param = Params::new(); param.set(Param::MsgId, msg.id.to_u32().to_string()); - job::add(context, Action::SendMdn, msg.from_id as i32, param, 0).await; + add(context, Action::SendMdn, msg.from_id as i32, param, 0).await; Ok(()) } @@ -1149,30 +953,36 @@ pub async fn add( ] ).await.ok(); - match thread { - Thread::Imap => interrupt_inbox_idle(context).await, - Thread::Smtp => interrupt_smtp_idle(context).await, - Thread::Unknown => {} + match action { + Action::Unknown => unreachable!(), + Action::Housekeeping + | Action::EmptyServer + | Action::DeleteMsgOnImap + | Action::MarkseenMdnOnImap + | Action::MarkseenMsgOnImap + | Action::MoveMsg => { + context.interrupt_inbox().await; + } + Action::MaybeSendLocations + | Action::MaybeSendLocationsEnded + | Action::SendMdn + | Action::SendMsgToSmtp => { + context.interrupt_smtp().await; + } } } -pub async fn interrupt_smtp_idle(context: &Context) { - unimplemented!(); - // info!(context, "Interrupting SMTP-idle...",); - - // context.smtp.state.write().await.perform_jobs_needed = PerformJobsNeeded::AtOnce; - // context.smtp.notify_sender.send(()).await; - - // info!(context, "Interrupting SMTP-idle... ended",); -} - /// Load jobs from the database. /// /// Load jobs for this "[Thread]", i.e. either load SMTP jobs or load /// IMAP jobs. The `probe_network` parameter decides how to query /// jobs, this is tricky and probably wrong currently. Look at the /// SQL queries for details. -async fn load_next_job(context: &Context, thread: Thread, probe_network: bool) -> Option { +pub(crate) async fn load_next( + context: &Context, + thread: Thread, + probe_network: bool, +) -> Option { let query = if !probe_network { // processing for first-try and after backoff-timeouts: // process jobs in the order they were added. @@ -1263,11 +1073,11 @@ mod tests { // all jobs. let t = dummy_context().await; insert_job(&t.ctx, -1).await; // This can not be loaded into Job struct. - let jobs = load_next_job(&t.ctx, Thread::from(Action::MoveMsg), false).await; + let jobs = load_next(&t.ctx, Thread::from(Action::MoveMsg), false).await; assert!(jobs.is_none()); insert_job(&t.ctx, 1).await; - let jobs = load_next_job(&t.ctx, Thread::from(Action::MoveMsg), false).await; + let jobs = load_next(&t.ctx, Thread::from(Action::MoveMsg), false).await; assert!(jobs.is_some()); } } diff --git a/src/scheduler.rs b/src/scheduler.rs index f010b2359..63ebedf7e 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -1,9 +1,12 @@ use async_std::prelude::*; use async_std::sync::{channel, Receiver, Sender}; +use async_std::task; -const MAX_JOBS_WAITING: usize = 50; +use std::time::Duration; +use crate::context::Context; use crate::imap::Imap; +use crate::job::{self, Thread}; use crate::smtp::Smtp; /// Job and connection scheduler. @@ -11,16 +14,34 @@ use crate::smtp::Smtp; pub(crate) enum Scheduler { Stopped, Running { - inbox: ImapConnectionState, - mvbox: ImapConnectionState, - sentbox: ImapConnectionState, + inbox: ImapConnectionState, + mvbox: ImapConnectionState, + sentbox: ImapConnectionState, smtp: SmtpConnectionState, }, } +impl Context { + pub(crate) async fn interrupt_inbox(&self) { + self.scheduler.read().await.interrupt_inbox().await; + } + + pub(crate) async fn interrupt_sentbox(&self) { + self.scheduler.read().await.interrupt_sentbox().await; + } + + pub(crate) async fn interrupt_mvbox(&self) { + self.scheduler.read().await.interrupt_mvbox().await; + } + + pub(crate) async fn interrupt_smtp(&self) { + self.scheduler.read().await.interrupt_smtp().await; + } +} + impl Scheduler { /// Start the scheduler, panics if it is already running. - pub async fn run(&mut self) { + pub async fn run(&mut self, ctx: Context) { match self { Scheduler::Stopped => { let ( @@ -34,13 +55,96 @@ impl Scheduler { .join(ImapConnectionState::new()) .join(SmtpConnectionState::new()) .await; - *self = Scheduler::Running { inbox, mvbox, sentbox, smtp, }; + + let ctx1 = ctx.clone(); + task::spawn(async move { + let ImapConnectionHandlers { + mut connection, + stop_receiver, + shutdown_sender, + } = inbox_handlers; + + let fut = async move { + loop { + // TODO: correct value + let probe_network = false; + match job::load_next(&ctx1, Thread::Imap, probe_network) + .timeout(Duration::from_millis(200)) + .await + { + Ok(Some(job)) => { + job::perform_job( + &ctx1, + job::Connection::Inbox(&mut connection), + job, + ) + .await; + } + Ok(None) | Err(async_std::future::TimeoutError { .. }) => { + // fetch + connection.fetch(&ctx1, "TODO").await; + + // idle + connection.idle(&ctx1, Some("TODO".into())).await; + } + } + } + }; + + fut.race(stop_receiver.recv()).await; + shutdown_sender.send(()).await; + }); + + // TODO: mvbox + + // TODO: sentbox + + let ctx1 = ctx.clone(); + task::spawn(async move { + let SmtpConnectionHandlers { + mut connection, + stop_receiver, + shutdown_sender, + idle_interrupt_receiver, + } = smtp_handlers; + + let fut = async move { + loop { + // TODO: correct value + let probe_network = false; + match job::load_next(&ctx1, Thread::Smtp, probe_network) + .timeout(Duration::from_millis(200)) + .await + { + Ok(Some(job)) => { + job::perform_job( + &ctx1, + job::Connection::Smtp(&mut connection), + job, + ) + .await; + } + Ok(None) | Err(async_std::future::TimeoutError { .. }) => { + use futures::future::FutureExt; + + // Fake Idle + async_std::task::sleep(Duration::from_millis(500)) + .race(idle_interrupt_receiver.recv().map(|_| ())) + .await; + } + } + } + }; + + fut.race(stop_receiver.recv()).await; + shutdown_sender.send(()).await; + }); } Scheduler::Running { .. } => { // TODO: return an error @@ -49,6 +153,41 @@ impl Scheduler { } } + fn inbox(&self) -> Option<&ImapConnectionState> { + match self { + Scheduler::Running { ref inbox, .. } => Some(inbox), + _ => None, + } + } + + async fn interrupt_inbox(&self) { + match self { + Scheduler::Running { ref inbox, .. } => inbox.interrupt().await, + _ => panic!("interrupt_imap must be called in running mode"), + } + } + + async fn interrupt_mvbox(&self) { + match self { + Scheduler::Running { ref mvbox, .. } => mvbox.interrupt().await, + _ => panic!("interrupt_mvbox must be called in running mode"), + } + } + + async fn interrupt_sentbox(&self) { + match self { + Scheduler::Running { ref sentbox, .. } => sentbox.interrupt().await, + _ => panic!("interrupt_sentbox must be called in running mode"), + } + } + + async fn interrupt_smtp(&self) { + match self { + Scheduler::Running { ref smtp, .. } => smtp.interrupt().await, + _ => panic!("interrupt_smtp must be called in running mode"), + } + } + /// Halt the scheduler, panics if it is already stopped. pub async fn stop(&mut self) { match self { @@ -90,54 +229,51 @@ impl Scheduler { /// Connection state logic shared between imap and smtp connections. #[derive(Debug)] -struct ConnectionState { +struct ConnectionState { /// Channel to notify that shutdown has completed. shutdown_receiver: Receiver<()>, /// Channel to interrupt the whole connection. stop_sender: Sender<()>, - /// Channel to receive new jobs. - jobs_receiver: Receiver, - /// Channel to schedule new jobs. - jobs_sender: Sender, + /// Channel to interrupt idle. + idle_interrupt_sender: Sender<()>, } -impl ConnectionState { - /// Send a new job. - pub async fn send_job(&self, job: T) { - self.jobs_sender.send(job).await; - } - +impl ConnectionState { /// Shutdown this connection completely. - pub async fn stop(&self) { + async fn stop(&self) { // Trigger shutdown of the run loop. self.stop_sender.send(()).await; // Wait for a notification that the run loop has been shutdown. self.shutdown_receiver.recv().await; } + + async fn interrupt(&self) { + self.idle_interrupt_sender.send(()).await; + } } #[derive(Debug)] pub(crate) struct SmtpConnectionState { - state: ConnectionState, + state: ConnectionState, } impl SmtpConnectionState { async fn new() -> (Self, SmtpConnectionHandlers) { - let (jobs_sender, jobs_receiver) = channel(50); let (stop_sender, stop_receiver) = channel(1); let (shutdown_sender, shutdown_receiver) = channel(1); + let (idle_interrupt_sender, idle_interrupt_receiver) = channel(1); let handlers = SmtpConnectionHandlers { connection: Smtp::new(), stop_receiver, shutdown_sender, + idle_interrupt_receiver, }; let state = ConnectionState { + idle_interrupt_sender, shutdown_receiver, stop_sender, - jobs_sender, - jobs_receiver, }; let conn = SmtpConnectionState { state }; @@ -145,9 +281,9 @@ impl SmtpConnectionState { (conn, handlers) } - /// Send a new job. - async fn send_job(&self, job: SmtpJob) { - self.state.send_job(job).await; + /// Interrupt any form of idle. + async fn interrupt(&self) { + self.state.interrupt().await; } /// Shutdown this connection completely. @@ -161,19 +297,17 @@ struct SmtpConnectionHandlers { connection: Smtp, stop_receiver: Receiver<()>, shutdown_sender: Sender<()>, + idle_interrupt_receiver: Receiver<()>, } #[derive(Debug)] -pub(crate) struct ImapConnectionState { - /// Channel to interrupt idle. - idle_interrupt_sender: Sender<()>, - state: ConnectionState, +pub(crate) struct ImapConnectionState { + state: ConnectionState, } -impl ImapConnectionState { +impl ImapConnectionState { /// Construct a new connection. async fn new() -> (Self, ImapConnectionHandlers) { - let (jobs_sender, jobs_receiver) = channel(MAX_JOBS_WAITING); let (stop_sender, stop_receiver) = channel(1); let (idle_interrupt_sender, idle_interrupt_receiver) = channel(1); let (shutdown_sender, shutdown_receiver) = channel(1); @@ -185,26 +319,19 @@ impl ImapConnectionState { }; let state = ConnectionState { + idle_interrupt_sender, shutdown_receiver, stop_sender, - jobs_sender, - jobs_receiver, }; - let conn = ImapConnectionState { - idle_interrupt_sender, - state, - }; + let conn = ImapConnectionState { state }; (conn, handlers) } - /// Send a new job. - async fn send_job(&self, job: T) { - self.state - .send_job(job) - .join(self.idle_interrupt_sender.send(())) - .await; + /// Interrupt any form of idle. + async fn interrupt(&self) { + self.state.interrupt().await; } /// Shutdown this connection completely. @@ -219,19 +346,3 @@ struct ImapConnectionHandlers { stop_receiver: Receiver<()>, shutdown_sender: Sender<()>, } - -/// Jobs handled by the inbox connection. -#[derive(Debug)] -pub enum InboxJob {} - -/// Jobs handled by the mvbox connection. -#[derive(Debug)] -pub enum MvboxJob {} - -/// Jobs handled by the sentbox connection. -#[derive(Debug)] -pub enum SentboxJob {} - -/// Jobs handled by the smtp connection. -#[derive(Debug)] -pub enum SmtpJob {} From aa45716ef7a7e73b958bd1225b37a5805a0052d0 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 18 Mar 2020 00:36:13 +0100 Subject: [PATCH 014/118] cleanup and setup configure --- src/config.rs | 1 - src/configure/mod.rs | 934 ++++++++++++++++++++++--------------------- src/context.rs | 16 - src/imap/idle.rs | 1 - src/imap/mod.rs | 4 +- src/imex.rs | 2 +- src/job.rs | 7 +- src/job_thread.rs | 218 ---------- src/lib.rs | 1 - src/scheduler.rs | 8 - src/smtp/mod.rs | 3 +- 11 files changed, 475 insertions(+), 720 deletions(-) delete mode 100644 src/job_thread.rs diff --git a/src/config.rs b/src/config.rs index 76832d014..a39b54bca 100644 --- a/src/config.rs +++ b/src/config.rs @@ -7,7 +7,6 @@ use crate::blob::BlobObject; use crate::constants::DC_VERSION_STR; use crate::context::Context; use crate::dc_tools::*; -use crate::job::*; use crate::mimefactory::RECOMMENDED_FILE_SIZE; use crate::stock::StockMessage; diff --git a/src/configure/mod.rs b/src/configure/mod.rs index 1fcf132b2..b7a896af9 100644 --- a/src/configure/mod.rs +++ b/src/configure/mod.rs @@ -10,10 +10,10 @@ use crate::config::Config; use crate::constants::*; use crate::context::Context; use crate::dc_tools::*; -use crate::job; +use crate::imap::Imap; use crate::login_param::{CertificateChecks, LoginParam}; use crate::oauth2::*; -use crate::param::Params; +use crate::smtp::Smtp; use crate::{chat, e2ee, provider}; use crate::message::Message; @@ -31,424 +31,417 @@ macro_rules! progress { } impl Context { + /// Checks if the context is already configured. + pub async fn is_configured(&self) -> bool { + self.sql.get_raw_config_bool(self, "configured").await + } + /// Starts a configuration job. pub async fn configure(&self) { if self.has_ongoing().await { warn!(self, "There is already another ongoing process running.",); return; } - // job::kill_action(self, job::Action::ConfigureImap).await; - // job::add(self, job::Action::ConfigureImap, 0, Params::new(), 0).await; + + if self.scheduler.read().await.is_running() { + warn!(self, "Can not configure, already running"); + return; + } + + if !self.sql.is_open().await { + error!(self, "Cannot configure, database not opened.",); + progress!(self, 0); + return; + } + if !self.alloc_ongoing().await { + error!(self, "Cannot allocate ongoing process"); + progress!(self, 0); + return; + } + let mut success = false; + let mut param_autoconfig: Option = None; + + info!(self, "Configure ...",); + + // Variables that are shared between steps: + let mut param = LoginParam::from_database(self, "").await; + // need all vars here to be mutable because rust thinks the same step could be called multiple times + // and also initialize, because otherwise rust thinks it's used while unitilized, even if thats not the case as the loop goes only forward + let mut param_domain = "undefined.undefined".to_owned(); + let mut param_addr_urlencoded: String = + "Internal Error: this value should never be used".to_owned(); + let mut keep_flags = 0; + + const STEP_12_USE_AUTOCONFIG: u8 = 12; + const STEP_13_AFTER_AUTOCONFIG: u8 = 13; + + let mut step_counter: u8 = 0; + let (_s, r) = async_std::sync::channel(1); + let mut imap = Imap::new(r); + let mut is_imap_connected = false; + + while !self.shall_stop_ongoing().await { + step_counter += 1; + + let success = match step_counter { + // Read login parameters from the database + 1 => { + progress!(self, 1); + if param.addr.is_empty() { + error!(self, "Please enter an email address.",); + } + !param.addr.is_empty() + } + // Step 1: Load the parameters and check email-address and password + 2 => { + if 0 != param.server_flags & DC_LP_AUTH_OAUTH2 { + // the used oauth2 addr may differ, check this. + // if dc_get_oauth2_addr() is not available in the oauth2 implementation, + // just use the given one. + progress!(self, 10); + if let Some(oauth2_addr) = + dc_get_oauth2_addr(self, ¶m.addr, ¶m.mail_pw) + .await + .and_then(|e| e.parse().ok()) + { + info!(self, "Authorized address is {}", oauth2_addr); + param.addr = oauth2_addr; + self.sql + .set_raw_config(self, "addr", Some(param.addr.as_str())) + .await + .ok(); + } + progress!(self, 20); + } + true // no oauth? - just continue it's no error + } + 3 => { + if let Ok(parsed) = param.addr.parse() { + let parsed: EmailAddress = parsed; + param_domain = parsed.domain; + param_addr_urlencoded = + utf8_percent_encode(¶m.addr, NON_ALPHANUMERIC).to_string(); + true + } else { + error!(self, "Bad email-address."); + false + } + } + // Step 2: Autoconfig + 4 => { + progress!(self, 200); + + if param.mail_server.is_empty() + && param.mail_port == 0 + /*&¶m.mail_user.is_empty() -- the user can enter a loginname which is used by autoconfig then */ + && param.send_server.is_empty() + && param.send_port == 0 + && param.send_user.is_empty() + /*&¶m.send_pw.is_empty() -- the password cannot be auto-configured and is no criterion for autoconfig or not */ + && (param.server_flags & !DC_LP_AUTH_OAUTH2) == 0 + { + // no advanced parameters entered by the user: query provider-database or do Autoconfig + keep_flags = param.server_flags & DC_LP_AUTH_OAUTH2; + if let Some(new_param) = get_offline_autoconfig(self, ¶m) { + // got parameters from our provider-database, skip Autoconfig, preserve the OAuth2 setting + param_autoconfig = Some(new_param); + step_counter = STEP_12_USE_AUTOCONFIG - 1; // minus one as step_counter is increased on next loop + } + } else { + // advanced parameters entered by the user: skip Autoconfig + step_counter = STEP_13_AFTER_AUTOCONFIG - 1; // minus one as step_counter is increased on next loop + } + true + } + /* A. Search configurations from the domain used in the email-address, prefer encrypted */ + 5 => { + if param_autoconfig.is_none() { + let url = format!( + "https://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}", + param_domain, param_addr_urlencoded + ); + param_autoconfig = moz_autoconfigure(self, &url, ¶m).ok(); + } + true + } + 6 => { + progress!(self, 300); + if param_autoconfig.is_none() { + // the doc does not mention `emailaddress=`, however, Thunderbird adds it, see https://releases.mozilla.org/pub/thunderbird/ , which makes some sense + let url = format!( + "https://{}/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress={}", + param_domain, param_addr_urlencoded + ); + param_autoconfig = moz_autoconfigure(self, &url, ¶m).ok(); + } + true + } + /* Outlook section start ------------- */ + /* Outlook uses always SSL but different domains (this comment describes the next two steps) */ + 7 => { + progress!(self, 310); + if param_autoconfig.is_none() { + let url = format!("https://{}/autodiscover/autodiscover.xml", param_domain); + param_autoconfig = outlk_autodiscover(self, &url, ¶m).ok(); + } + true + } + 8 => { + progress!(self, 320); + if param_autoconfig.is_none() { + let url = format!( + "https://{}{}/autodiscover/autodiscover.xml", + "autodiscover.", param_domain + ); + param_autoconfig = outlk_autodiscover(self, &url, ¶m).ok(); + } + true + } + /* ----------- Outlook section end */ + 9 => { + progress!(self, 330); + if param_autoconfig.is_none() { + let url = format!( + "http://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}", + param_domain, param_addr_urlencoded + ); + param_autoconfig = moz_autoconfigure(self, &url, ¶m).ok(); + } + true + } + 10 => { + progress!(self, 340); + if param_autoconfig.is_none() { + // do not transfer the email-address unencrypted + let url = format!( + "http://{}/.well-known/autoconfig/mail/config-v1.1.xml", + param_domain + ); + param_autoconfig = moz_autoconfigure(self, &url, ¶m).ok(); + } + true + } + /* B. If we have no configuration yet, search configuration in Thunderbird's centeral database */ + 11 => { + progress!(self, 350); + if param_autoconfig.is_none() { + /* always SSL for Thunderbird's database */ + let url = + format!("https://autoconfig.thunderbird.net/v1.1/{}", param_domain); + param_autoconfig = moz_autoconfigure(self, &url, ¶m).ok(); + } + true + } + /* C. Do we have any autoconfig result? + If you change the match-number here, also update STEP_12_COPY_AUTOCONFIG above + */ + STEP_12_USE_AUTOCONFIG => { + progress!(self, 500); + if let Some(ref cfg) = param_autoconfig { + info!(self, "Got autoconfig: {}", &cfg); + if !cfg.mail_user.is_empty() { + param.mail_user = cfg.mail_user.clone(); + } + param.mail_server = cfg.mail_server.clone(); /* all other values are always NULL when entering autoconfig */ + param.mail_port = cfg.mail_port; + param.send_server = cfg.send_server.clone(); + param.send_port = cfg.send_port; + param.send_user = cfg.send_user.clone(); + param.server_flags = cfg.server_flags; + /* although param_autoconfig's data are no longer needed from, + it is used to later to prevent trying variations of port/server/logins */ + } + param.server_flags |= keep_flags; + true + } + // Step 3: Fill missing fields with defaults + // If you change the match-number here, also update STEP_13_AFTER_AUTOCONFIG above + STEP_13_AFTER_AUTOCONFIG => { + if param.mail_server.is_empty() { + param.mail_server = format!("imap.{}", param_domain,) + } + if param.mail_port == 0 { + param.mail_port = if 0 != param.server_flags & (0x100 | 0x400) { + 143 + } else { + 993 + } + } + if param.mail_user.is_empty() { + param.mail_user = param.addr.clone(); + } + if param.send_server.is_empty() && !param.mail_server.is_empty() { + param.send_server = param.mail_server.clone(); + if param.send_server.starts_with("imap.") { + param.send_server = param.send_server.replacen("imap", "smtp", 1); + } + } + if param.send_port == 0 { + param.send_port = + if 0 != param.server_flags & DC_LP_SMTP_SOCKET_STARTTLS as i32 { + 587 + } else if 0 != param.server_flags & DC_LP_SMTP_SOCKET_PLAIN as i32 { + 25 + } else { + 465 + } + } + if param.send_user.is_empty() && !param.mail_user.is_empty() { + param.send_user = param.mail_user.clone(); + } + if param.send_pw.is_empty() && !param.mail_pw.is_empty() { + param.send_pw = param.mail_pw.clone() + } + if !dc_exactly_one_bit_set(param.server_flags & DC_LP_AUTH_FLAGS as i32) { + param.server_flags &= !(DC_LP_AUTH_FLAGS as i32); + param.server_flags |= DC_LP_AUTH_NORMAL as i32 + } + if !dc_exactly_one_bit_set(param.server_flags & DC_LP_IMAP_SOCKET_FLAGS as i32) + { + param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS as i32); + param.server_flags |= if param.send_port == 143 { + DC_LP_IMAP_SOCKET_STARTTLS as i32 + } else { + DC_LP_IMAP_SOCKET_SSL as i32 + } + } + if !dc_exactly_one_bit_set( + param.server_flags & (DC_LP_SMTP_SOCKET_FLAGS as i32), + ) { + param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32); + param.server_flags |= if param.send_port == 587 { + DC_LP_SMTP_SOCKET_STARTTLS as i32 + } else if param.send_port == 25 { + DC_LP_SMTP_SOCKET_PLAIN as i32 + } else { + DC_LP_SMTP_SOCKET_SSL as i32 + } + } + /* do we have a complete configuration? */ + if param.mail_server.is_empty() + || param.mail_port == 0 + || param.mail_user.is_empty() + || param.mail_pw.is_empty() + || param.send_server.is_empty() + || param.send_port == 0 + || param.send_user.is_empty() + || param.send_pw.is_empty() + || param.server_flags == 0 + { + error!(self, "Account settings incomplete."); + false + } else { + true + } + } + 14 => { + progress!(self, 600); + /* try to connect to IMAP - if we did not got an autoconfig, + do some further tries with different settings and username variations */ + is_imap_connected = try_imap_connections( + self, + &mut param, + param_autoconfig.is_some(), + &mut imap, + ) + .await; + is_imap_connected + } + 15 => { + progress!(self, 800); + try_smtp_connections(self, &mut param, param_autoconfig.is_some()).await + } + 16 => { + progress!(self, 900); + let create_mvbox = self.get_config_bool(Config::MvboxWatch).await + || self.get_config_bool(Config::MvboxMove).await; + if let Err(err) = imap.ensure_configured_folders(self, create_mvbox).await { + warn!(self, "configuring folders failed: {:?}", err); + false + } else { + let res = imap.select_with_uidvalidity(self, "INBOX").await; + if let Err(err) = res { + error!(self, "could not read INBOX status: {:?}", err); + false + } else { + true + } + } + } + 17 => { + progress!(self, 910); + /* configuration success - write back the configured parameters with the "configured_" prefix; also write the "configured"-flag */ + param + .save_to_database( + self, + "configured_", /*the trailing underscore is correct*/ + ) + .await + .ok(); + + self.sql + .set_raw_config_bool(self, "configured", true) + .await + .ok(); + true + } + 18 => { + progress!(self, 920); + // we generate the keypair just now - we could also postpone this until the first message is sent, however, + // this may result in a unexpected and annoying delay when the user sends his very first message + // (~30 seconds on a Moto G4 play) and might looks as if message sending is always that slow. + success = e2ee::ensure_secret_key_exists(self).await.is_ok(); + info!(self, "key generation completed"); + progress!(self, 940); + break; // We are done here + } + _ => { + error!(self, "Internal error: step counter out of bound",); + break; + } + }; + + if !success { + break; + } + } + if is_imap_connected { + imap.disconnect(self).await; + } + + // remember the entered parameters on success + // and restore to last-entered on failure. + // this way, the parameters visible to the ui are always in-sync with the current configuration. + if success { + LoginParam::from_database(self, "") + .await + .save_to_database(self, "configured_raw_") + .await + .ok(); + } else { + LoginParam::from_database(self, "configured_raw_") + .await + .save_to_database(self, "") + .await + .ok(); + } + + if let Some(provider) = provider::get_provider_info(¶m.addr) { + if !provider.after_login_hint.is_empty() { + let mut msg = Message::new(Viewtype::Text); + msg.text = Some(provider.after_login_hint.to_string()); + if chat::add_device_msg(self, Some("core-provider-info"), Some(&mut msg)) + .await + .is_err() + { + warn!(self, "cannot add after_login_hint as core-provider-info"); + } + } + } + + self.free_ongoing().await; + progress!(self, if success { 1000 } else { 0 }); } - - /// Checks if the context is already configured. - pub async fn is_configured(&self) -> bool { - self.sql.get_raw_config_bool(self, "configured").await - } -} - -/******************************************************************************* - * Configure JOB - ******************************************************************************/ -#[allow(clippy::cognitive_complexity)] -pub(crate) async fn job_configure_imap(context: &Context) -> job::Status { - unimplemented!() - // if !context.sql.is_open().await { - // error!(context, "Cannot configure, database not opened.",); - // progress!(context, 0); - // return job::Status::Finished(Err(format_err!("Database not opened"))); - // } - // if !context.alloc_ongoing().await { - // progress!(context, 0); - // return job::Status::Finished(Err(format_err!("Cannot allocated ongoing process"))); - // } - // let mut success = false; - // let mut imap_connected_here = false; - // let mut smtp_connected_here = false; - - // let mut param_autoconfig: Option = None; - - // // context.inbox_thread.imap.disconnect(context).await; - // // context.sentbox_thread.imap.disconnect(context).await; - // // context.mvbox_thread.imap.disconnect(context).await; - // // context.smtp.disconnect().await; - - // info!(context, "Configure ...",); - - // // Variables that are shared between steps: - // let mut param = LoginParam::from_database(context, "").await; - // // need all vars here to be mutable because rust thinks the same step could be called multiple times - // // and also initialize, because otherwise rust thinks it's used while unitilized, even if thats not the case as the loop goes only forward - // let mut param_domain = "undefined.undefined".to_owned(); - // let mut param_addr_urlencoded: String = - // "Internal Error: this value should never be used".to_owned(); - // let mut keep_flags = 0; - - // const STEP_12_USE_AUTOCONFIG: u8 = 12; - // const STEP_13_AFTER_AUTOCONFIG: u8 = 13; - - // let mut step_counter: u8 = 0; - // while !context.shall_stop_ongoing().await { - // step_counter += 1; - - // let success = match step_counter { - // // Read login parameters from the database - // 1 => { - // progress!(context, 1); - // if param.addr.is_empty() { - // error!(context, "Please enter an email address.",); - // } - // !param.addr.is_empty() - // } - // // Step 1: Load the parameters and check email-address and password - // 2 => { - // if 0 != param.server_flags & DC_LP_AUTH_OAUTH2 { - // // the used oauth2 addr may differ, check this. - // // if dc_get_oauth2_addr() is not available in the oauth2 implementation, - // // just use the given one. - // progress!(context, 10); - // if let Some(oauth2_addr) = - // dc_get_oauth2_addr(context, ¶m.addr, ¶m.mail_pw) - // .await - // .and_then(|e| e.parse().ok()) - // { - // info!(context, "Authorized address is {}", oauth2_addr); - // param.addr = oauth2_addr; - // context - // .sql - // .set_raw_config(context, "addr", Some(param.addr.as_str())) - // .await - // .ok(); - // } - // progress!(context, 20); - // } - // true // no oauth? - just continue it's no error - // } - // 3 => { - // if let Ok(parsed) = param.addr.parse() { - // let parsed: EmailAddress = parsed; - // param_domain = parsed.domain; - // param_addr_urlencoded = - // utf8_percent_encode(¶m.addr, NON_ALPHANUMERIC).to_string(); - // true - // } else { - // error!(context, "Bad email-address."); - // false - // } - // } - // // Step 2: Autoconfig - // 4 => { - // progress!(context, 200); - - // if param.mail_server.is_empty() - // && param.mail_port == 0 - // /*&¶m.mail_user.is_empty() -- the user can enter a loginname which is used by autoconfig then */ - // && param.send_server.is_empty() - // && param.send_port == 0 - // && param.send_user.is_empty() - // /*&¶m.send_pw.is_empty() -- the password cannot be auto-configured and is no criterion for autoconfig or not */ - // && (param.server_flags & !DC_LP_AUTH_OAUTH2) == 0 - // { - // // no advanced parameters entered by the user: query provider-database or do Autoconfig - // keep_flags = param.server_flags & DC_LP_AUTH_OAUTH2; - // if let Some(new_param) = get_offline_autoconfig(context, ¶m) { - // // got parameters from our provider-database, skip Autoconfig, preserve the OAuth2 setting - // param_autoconfig = Some(new_param); - // step_counter = STEP_12_USE_AUTOCONFIG - 1; // minus one as step_counter is increased on next loop - // } - // } else { - // // advanced parameters entered by the user: skip Autoconfig - // step_counter = STEP_13_AFTER_AUTOCONFIG - 1; // minus one as step_counter is increased on next loop - // } - // true - // } - // /* A. Search configurations from the domain used in the email-address, prefer encrypted */ - // 5 => { - // if param_autoconfig.is_none() { - // let url = format!( - // "https://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}", - // param_domain, param_addr_urlencoded - // ); - // param_autoconfig = moz_autoconfigure(context, &url, ¶m).ok(); - // } - // true - // } - // 6 => { - // progress!(context, 300); - // if param_autoconfig.is_none() { - // // the doc does not mention `emailaddress=`, however, Thunderbird adds it, see https://releases.mozilla.org/pub/thunderbird/ , which makes some sense - // let url = format!( - // "https://{}/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress={}", - // param_domain, param_addr_urlencoded - // ); - // param_autoconfig = moz_autoconfigure(context, &url, ¶m).ok(); - // } - // true - // } - // /* Outlook section start ------------- */ - // /* Outlook uses always SSL but different domains (this comment describes the next two steps) */ - // 7 => { - // progress!(context, 310); - // if param_autoconfig.is_none() { - // let url = format!("https://{}/autodiscover/autodiscover.xml", param_domain); - // param_autoconfig = outlk_autodiscover(context, &url, ¶m).ok(); - // } - // true - // } - // 8 => { - // progress!(context, 320); - // if param_autoconfig.is_none() { - // let url = format!( - // "https://{}{}/autodiscover/autodiscover.xml", - // "autodiscover.", param_domain - // ); - // param_autoconfig = outlk_autodiscover(context, &url, ¶m).ok(); - // } - // true - // } - // /* ----------- Outlook section end */ - // 9 => { - // progress!(context, 330); - // if param_autoconfig.is_none() { - // let url = format!( - // "http://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}", - // param_domain, param_addr_urlencoded - // ); - // param_autoconfig = moz_autoconfigure(context, &url, ¶m).ok(); - // } - // true - // } - // 10 => { - // progress!(context, 340); - // if param_autoconfig.is_none() { - // // do not transfer the email-address unencrypted - // let url = format!( - // "http://{}/.well-known/autoconfig/mail/config-v1.1.xml", - // param_domain - // ); - // param_autoconfig = moz_autoconfigure(context, &url, ¶m).ok(); - // } - // true - // } - // /* B. If we have no configuration yet, search configuration in Thunderbird's centeral database */ - // 11 => { - // progress!(context, 350); - // if param_autoconfig.is_none() { - // /* always SSL for Thunderbird's database */ - // let url = format!("https://autoconfig.thunderbird.net/v1.1/{}", param_domain); - // param_autoconfig = moz_autoconfigure(context, &url, ¶m).ok(); - // } - // true - // } - // /* C. Do we have any autoconfig result? - // If you change the match-number here, also update STEP_12_COPY_AUTOCONFIG above - // */ - // STEP_12_USE_AUTOCONFIG => { - // progress!(context, 500); - // if let Some(ref cfg) = param_autoconfig { - // info!(context, "Got autoconfig: {}", &cfg); - // if !cfg.mail_user.is_empty() { - // param.mail_user = cfg.mail_user.clone(); - // } - // param.mail_server = cfg.mail_server.clone(); /* all other values are always NULL when entering autoconfig */ - // param.mail_port = cfg.mail_port; - // param.send_server = cfg.send_server.clone(); - // param.send_port = cfg.send_port; - // param.send_user = cfg.send_user.clone(); - // param.server_flags = cfg.server_flags; - // /* although param_autoconfig's data are no longer needed from, - // it is used to later to prevent trying variations of port/server/logins */ - // } - // param.server_flags |= keep_flags; - // true - // } - // // Step 3: Fill missing fields with defaults - // // If you change the match-number here, also update STEP_13_AFTER_AUTOCONFIG above - // STEP_13_AFTER_AUTOCONFIG => { - // if param.mail_server.is_empty() { - // param.mail_server = format!("imap.{}", param_domain,) - // } - // if param.mail_port == 0 { - // param.mail_port = if 0 != param.server_flags & (0x100 | 0x400) { - // 143 - // } else { - // 993 - // } - // } - // if param.mail_user.is_empty() { - // param.mail_user = param.addr.clone(); - // } - // if param.send_server.is_empty() && !param.mail_server.is_empty() { - // param.send_server = param.mail_server.clone(); - // if param.send_server.starts_with("imap.") { - // param.send_server = param.send_server.replacen("imap", "smtp", 1); - // } - // } - // if param.send_port == 0 { - // param.send_port = if 0 != param.server_flags & DC_LP_SMTP_SOCKET_STARTTLS as i32 - // { - // 587 - // } else if 0 != param.server_flags & DC_LP_SMTP_SOCKET_PLAIN as i32 { - // 25 - // } else { - // 465 - // } - // } - // if param.send_user.is_empty() && !param.mail_user.is_empty() { - // param.send_user = param.mail_user.clone(); - // } - // if param.send_pw.is_empty() && !param.mail_pw.is_empty() { - // param.send_pw = param.mail_pw.clone() - // } - // if !dc_exactly_one_bit_set(param.server_flags & DC_LP_AUTH_FLAGS as i32) { - // param.server_flags &= !(DC_LP_AUTH_FLAGS as i32); - // param.server_flags |= DC_LP_AUTH_NORMAL as i32 - // } - // if !dc_exactly_one_bit_set(param.server_flags & DC_LP_IMAP_SOCKET_FLAGS as i32) { - // param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS as i32); - // param.server_flags |= if param.send_port == 143 { - // DC_LP_IMAP_SOCKET_STARTTLS as i32 - // } else { - // DC_LP_IMAP_SOCKET_SSL as i32 - // } - // } - // if !dc_exactly_one_bit_set(param.server_flags & (DC_LP_SMTP_SOCKET_FLAGS as i32)) { - // param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32); - // param.server_flags |= if param.send_port == 587 { - // DC_LP_SMTP_SOCKET_STARTTLS as i32 - // } else if param.send_port == 25 { - // DC_LP_SMTP_SOCKET_PLAIN as i32 - // } else { - // DC_LP_SMTP_SOCKET_SSL as i32 - // } - // } - // /* do we have a complete configuration? */ - // if param.mail_server.is_empty() - // || param.mail_port == 0 - // || param.mail_user.is_empty() - // || param.mail_pw.is_empty() - // || param.send_server.is_empty() - // || param.send_port == 0 - // || param.send_user.is_empty() - // || param.send_pw.is_empty() - // || param.server_flags == 0 - // { - // error!(context, "Account settings incomplete."); - // false - // } else { - // true - // } - // } - // 14 => { - // progress!(context, 600); - // /* try to connect to IMAP - if we did not got an autoconfig, - // do some further tries with different settings and username variations */ - // imap_connected_here = - // try_imap_connections(context, &mut param, param_autoconfig.is_some()).await; - // imap_connected_here - // } - // 15 => { - // progress!(context, 800); - // smtp_connected_here = - // try_smtp_connections(context, &mut param, param_autoconfig.is_some()).await; - // smtp_connected_here - // } - // 16 => { - // progress!(context, 900); - // let create_mvbox = context.get_config_bool(Config::MvboxWatch).await - // || context.get_config_bool(Config::MvboxMove).await; - // let imap = &context.inbox_thread.imap; - // if let Err(err) = imap.ensure_configured_folders(context, create_mvbox).await { - // warn!(context, "configuring folders failed: {:?}", err); - // false - // } else { - // let res = imap.select_with_uidvalidity(context, "INBOX").await; - // if let Err(err) = res { - // error!(context, "could not read INBOX status: {:?}", err); - // false - // } else { - // true - // } - // } - // } - // 17 => { - // progress!(context, 910); - // /* configuration success - write back the configured parameters with the "configured_" prefix; also write the "configured"-flag */ - // param - // .save_to_database( - // context, - // "configured_", /*the trailing underscore is correct*/ - // ) - // .await - // .ok(); - - // context - // .sql - // .set_raw_config_bool(context, "configured", true) - // .await - // .ok(); - // true - // } - // 18 => { - // progress!(context, 920); - // // we generate the keypair just now - we could also postpone this until the first message is sent, however, - // // this may result in a unexpected and annoying delay when the user sends his very first message - // // (~30 seconds on a Moto G4 play) and might looks as if message sending is always that slow. - // success = e2ee::ensure_secret_key_exists(context).await.is_ok(); - // info!(context, "key generation completed"); - // progress!(context, 940); - // break; // We are done here - // } - // _ => { - // error!(context, "Internal error: step counter out of bound",); - // break; - // } - // }; - - // if !success { - // break; - // } - // } - // if imap_connected_here { - // context.inbox_thread.imap.disconnect(context).await; - // } - // if smtp_connected_here { - // context.smtp.disconnect().await; - // } - - // // remember the entered parameters on success - // // and restore to last-entered on failure. - // // this way, the parameters visible to the ui are always in-sync with the current configuration. - // if success { - // LoginParam::from_database(context, "") - // .await - // .save_to_database(context, "configured_raw_") - // .await - // .ok(); - // } else { - // LoginParam::from_database(context, "configured_raw_") - // .await - // .save_to_database(context, "") - // .await - // .ok(); - // } - - // if let Some(provider) = provider::get_provider_info(¶m.addr) { - // if !provider.after_login_hint.is_empty() { - // let mut msg = Message::new(Viewtype::Text); - // msg.text = Some(provider.after_login_hint.to_string()); - // if chat::add_device_msg(context, Some("core-provider-info"), Some(&mut msg)) - // .await - // .is_err() - // { - // warn!(context, "cannot add after_login_hint as core-provider-info"); - // } - // } - // } - - // context.free_ongoing().await; - // progress!(context, if success { 1000 } else { 0 }); - // job::Status::Finished(Ok(())) } #[allow(clippy::unnecessary_unwrap)] @@ -513,9 +506,10 @@ async fn try_imap_connections( context: &Context, mut param: &mut LoginParam, was_autoconfig: bool, + imap: &mut Imap, ) -> bool { // progress 650 and 660 - if let Some(res) = try_imap_connection(context, &mut param, was_autoconfig, 0).await { + if let Some(res) = try_imap_connection(context, &mut param, was_autoconfig, 0, imap).await { return res; } progress!(context, 670); @@ -530,7 +524,7 @@ async fn try_imap_connections( param.send_user = param.send_user.split_at(at).0.to_string(); } // progress 680 and 690 - if let Some(res) = try_imap_connection(context, &mut param, was_autoconfig, 1).await { + if let Some(res) = try_imap_connection(context, &mut param, was_autoconfig, 1, imap).await { res } else { false @@ -542,8 +536,9 @@ async fn try_imap_connection( param: &mut LoginParam, was_autoconfig: bool, variation: usize, + imap: &mut Imap, ) -> Option { - if let Some(res) = try_imap_one_param(context, ¶m).await { + if let Some(res) = try_imap_one_param(context, ¶m, imap).await { return Some(res); } if was_autoconfig { @@ -552,36 +547,39 @@ async fn try_imap_connection( progress!(context, 650 + variation * 30); param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS); param.server_flags |= DC_LP_IMAP_SOCKET_STARTTLS; - if let Some(res) = try_imap_one_param(context, ¶m).await { + if let Some(res) = try_imap_one_param(context, ¶m, imap).await { return Some(res); } progress!(context, 660 + variation * 30); param.mail_port = 143; - try_imap_one_param(context, ¶m).await + try_imap_one_param(context, ¶m, imap).await } -async fn try_imap_one_param(context: &Context, param: &LoginParam) -> Option { - unimplemented!(); - // let inf = format!( - // "imap: {}@{}:{} flags=0x{:x} certificate_checks={}", - // param.mail_user, - // param.mail_server, - // param.mail_port, - // param.server_flags, - // param.imap_certificate_checks - // ); - // info!(context, "Trying: {}", inf); - // if context.inbox_thread.imap.connect(context, ¶m).await { - // info!(context, "success: {}", inf); - // return Some(true); - // } - // if context.shall_stop_ongoing().await { - // return Some(false); - // } - // info!(context, "Could not connect: {}", inf); - // None +async fn try_imap_one_param( + context: &Context, + param: &LoginParam, + imap: &mut Imap, +) -> Option { + let inf = format!( + "imap: {}@{}:{} flags=0x{:x} certificate_checks={}", + param.mail_user, + param.mail_server, + param.mail_port, + param.server_flags, + param.imap_certificate_checks + ); + info!(context, "Trying: {}", inf); + if imap.connect(context, ¶m).await { + info!(context, "success: {}", inf); + return Some(true); + } + if context.shall_stop_ongoing().await { + return Some(false); + } + info!(context, "Could not connect: {}", inf); + None } async fn try_smtp_connections( @@ -589,8 +587,9 @@ async fn try_smtp_connections( mut param: &mut LoginParam, was_autoconfig: bool, ) -> bool { + let mut smtp = Smtp::new(); /* try to connect to SMTP - if we did not got an autoconfig, the first try was SSL-465 and we do a second try with STARTTLS-587 */ - if let Some(res) = try_smtp_one_param(context, ¶m).await { + if let Some(res) = try_smtp_one_param(context, ¶m, &mut smtp).await { return res; } if was_autoconfig { @@ -601,40 +600,49 @@ async fn try_smtp_connections( param.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32; param.send_port = 587; - if let Some(res) = try_smtp_one_param(context, ¶m).await { + if let Some(res) = try_smtp_one_param(context, ¶m, &mut smtp).await { + if res { + smtp.disconnect().await; + } return res; } progress!(context, 860); param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32); param.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32; param.send_port = 25; - if let Some(res) = try_smtp_one_param(context, ¶m).await { + if let Some(res) = try_smtp_one_param(context, ¶m, &mut smtp).await { + if res { + smtp.disconnect().await; + } return res; } false } -async fn try_smtp_one_param(context: &Context, param: &LoginParam) -> Option { - unimplemented!() - // let inf = format!( - // "smtp: {}@{}:{} flags: 0x{:x}", - // param.send_user, param.send_server, param.send_port, param.server_flags - // ); - // info!(context, "Trying: {}", inf); - // match context.smtp.connect(context, ¶m).await { - // Ok(()) => { - // info!(context, "success: {}", inf); - // Some(true) - // } - // Err(err) => { - // if context.shall_stop_ongoing().await { - // Some(false) - // } else { - // warn!(context, "could not connect: {}", err); - // None - // } - // } - // } +async fn try_smtp_one_param( + context: &Context, + param: &LoginParam, + smtp: &mut Smtp, +) -> Option { + let inf = format!( + "smtp: {}@{}:{} flags: 0x{:x}", + param.send_user, param.send_server, param.send_port, param.server_flags + ); + info!(context, "Trying: {}", inf); + match smtp.connect(context, ¶m).await { + Ok(()) => { + info!(context, "success: {}", inf); + Some(true) + } + Err(err) => { + if context.shall_stop_ongoing().await { + Some(false) + } else { + warn!(context, "could not connect: {}", err); + None + } + } + } } #[cfg(test)] @@ -655,7 +663,7 @@ mod tests { .set_config(Config::MailPw, Some("123456")) .await .unwrap(); - job_configure_imap(&t.ctx).await; + t.ctx.configure().await; } #[async_std::test] diff --git a/src/context.rs b/src/context.rs index 6499c2a26..1127f5509 100644 --- a/src/context.rs +++ b/src/context.rs @@ -14,16 +14,13 @@ use crate::constants::*; use crate::contact::*; use crate::error::*; use crate::events::Event; -use crate::imap::*; use crate::job::{self, Action}; -use crate::job_thread::JobThread; use crate::key::Key; use crate::login_param::LoginParam; use crate::lot::Lot; use crate::message::{self, Message, MessengerMessage, MsgId}; use crate::param::Params; use crate::scheduler::Scheduler; -use crate::smtp::Smtp; use crate::sql::Sql; #[derive(Clone, Debug)] @@ -492,19 +489,6 @@ pub(crate) struct BobStatus { pub qr_scan: Option, } -#[derive(Debug, PartialEq, Clone, Copy)] -pub(crate) enum PerformJobsNeeded { - Not, - AtOnce, - AvoidDos, -} - -impl Default for PerformJobsNeeded { - fn default() -> Self { - Self::Not - } -} - pub fn get_version_str() -> &'static str { &DC_VERSION_STR } diff --git a/src/imap/idle.rs b/src/imap/idle.rs index 1959bc8f8..89a410879 100644 --- a/src/imap/idle.rs +++ b/src/imap/idle.rs @@ -4,7 +4,6 @@ use async_imap::extensions::idle::{Handle as ImapIdleHandle, IdleResponse}; use async_native_tls::TlsStream; use async_std::net::TcpStream; use async_std::prelude::*; -use std::sync::atomic::Ordering; use std::time::{Duration, SystemTime}; use crate::context::Context; diff --git a/src/imap/mod.rs b/src/imap/mod.rs index a5f3fcda4..307d12fbf 100644 --- a/src/imap/mod.rs +++ b/src/imap/mod.rs @@ -3,8 +3,6 @@ //! uses [async-email/async-imap](https://github.com/async-email/async-imap) //! to implement connect, fetch, delete functionality with standard IMAP servers. -use std::sync::atomic::{AtomicBool, Ordering}; - use num_traits::FromPrimitive; use async_imap::{ @@ -12,7 +10,7 @@ use async_imap::{ types::{Capability, Fetch, Flag, Mailbox, Name, NameAttribute}, }; use async_std::prelude::*; -use async_std::sync::{Mutex, Receiver, RwLock}; +use async_std::sync::Receiver; use crate::config::*; use crate::constants::*; diff --git a/src/imex.rs b/src/imex.rs index 209ea856b..528db3c46 100644 --- a/src/imex.rs +++ b/src/imex.rs @@ -17,7 +17,7 @@ use crate::dc_tools::*; use crate::e2ee; use crate::error::*; use crate::events::Event; -use crate::job::{self, Action, Job}; +use crate::job::Job; use crate::key::{self, Key}; use crate::message::{Message, MsgId}; use crate::mimeparser::SystemMessage; diff --git a/src/job.rs b/src/job.rs index bc0329776..b223c5979 100644 --- a/src/job.rs +++ b/src/job.rs @@ -6,8 +6,6 @@ use std::future::Future; use std::{fmt, time}; -use async_std::prelude::*; - use deltachat_derive::{FromSql, ToSql}; use itertools::Itertools; use rand::{thread_rng, Rng}; @@ -15,16 +13,13 @@ use rand::{thread_rng, Rng}; use crate::blob::BlobObject; use crate::chat::{self, ChatId}; use crate::config::Config; -use crate::configure::*; use crate::constants::*; use crate::contact::Contact; -use crate::context::{Context, PerformJobsNeeded}; +use crate::context::Context; use crate::dc_tools::*; use crate::error::{Error, Result}; use crate::events::Event; use crate::imap::*; -use crate::imex::*; -use crate::job; use crate::location; use crate::login_param::LoginParam; use crate::message::MsgId; diff --git a/src/job_thread.rs b/src/job_thread.rs deleted file mode 100644 index 12d86bc50..000000000 --- a/src/job_thread.rs +++ /dev/null @@ -1,218 +0,0 @@ -use async_std::sync::{channel, Arc, Mutex, Receiver, Sender}; - -use crate::context::Context; -use crate::error::{Error, Result}; -use crate::imap::Imap; - -#[derive(Debug)] -pub struct JobThread { - pub name: &'static str, - pub folder_config_name: &'static str, - pub imap: Imap, - pub state: Arc>, - notify_sender: Sender<()>, - notify_receiver: Receiver<()>, -} - -#[derive(Clone, Debug, Default)] -pub struct JobState { - jobs_needed: bool, - suspended: bool, - using_handle: bool, -} - -impl JobThread { - pub fn new(name: &'static str, folder_config_name: &'static str, imap: Imap) -> Self { - let (notify_sender, notify_receiver) = channel(1); - - JobThread { - name, - folder_config_name, - imap, - state: Arc::new(Mutex::new(Default::default())), - notify_sender, - notify_receiver, - } - } - - pub async fn suspend(&mut self, context: &Context) { - info!(context, "Suspending {}-thread.", self.name,); - { - self.state.lock().await.suspended = true; - } - self.interrupt_idle(context).await; - loop { - let using_handle = self.state.lock().await.using_handle; - if !using_handle { - return; - } - async_std::task::sleep(std::time::Duration::from_micros(300 * 1000)).await; - } - } - - pub async fn unsuspend(&self, context: &Context) { - info!(context, "Unsuspending {}-thread.", self.name); - - { - let lock = &*self.state.clone(); - let mut state = lock.lock().await; - - state.suspended = false; - } - self.notify_sender.send(()).await; - } - - pub async fn try_interrupt_idle(&mut self, context: &Context) -> bool { - if self.state.lock().await.using_handle { - self.interrupt_idle(context).await; - return true; - } - - false - } - - pub async fn interrupt_idle(&mut self, context: &Context) { - { - self.state.lock().await.jobs_needed = true; - } - - info!(context, "Interrupting {}-IDLE...", self.name); - - self.imap.interrupt_idle(context).await; - - self.notify_sender.send(()).await; - - info!(context, "Interrupting {}-IDLE... finished", self.name); - } - - pub async fn fetch(&mut self, context: &Context, use_network: bool) { - { - let lock = &*self.state.clone(); - let mut state = lock.lock().await; - - if state.suspended { - return; - } - - state.using_handle = true; - } - - if use_network { - if let Err(err) = self.connect_and_fetch(context).await { - warn!(context, "connect+fetch failed: {}, reconnect & retry", err); - self.imap.trigger_reconnect(); - if let Err(err) = self.connect_and_fetch(context).await { - warn!(context, "connect+fetch failed: {}", err); - } - } - } - { - self.state.lock().await.using_handle = false; - } - } - - async fn connect_and_fetch(&mut self, context: &Context) -> Result<()> { - let prefix = format!("{}-fetch", self.name); - match self.imap.connect_configured(context).await { - Ok(()) => { - if let Some(watch_folder) = self.get_watch_folder(context).await { - let start = std::time::Instant::now(); - info!(context, "{} started...", prefix); - let res = self - .imap - .fetch(context, &watch_folder) - .await - .map_err(Into::into); - let elapsed = start.elapsed().as_millis(); - info!(context, "{} done in {:.3} ms.", prefix, elapsed); - - res - } else { - Err(Error::WatchFolderNotFound("not-set".to_string())) - } - } - Err(err) => Err(crate::error::Error::Message(err.to_string())), - } - } - - async fn get_watch_folder(&self, context: &Context) -> Option { - match context - .sql - .get_raw_config(context, self.folder_config_name) - .await - { - Some(name) => Some(name), - None => { - if self.folder_config_name == "configured_inbox_folder" { - // initialized with old version, so has not set configured_inbox_folder - Some("INBOX".to_string()) - } else { - None - } - } - } - } - - pub async fn idle(&mut self, context: &Context, use_network: bool) { - { - let lock = &*self.state.clone(); - let mut state = lock.lock().await; - - if state.jobs_needed { - info!( - context, - "{}-IDLE will not be started as it was interrupted while not idling.", - self.name, - ); - state.jobs_needed = false; - return; - } - - if state.suspended { - self.notify_receiver.recv().await; - return; - } - - state.using_handle = true; - - if !use_network { - state.using_handle = false; - self.notify_receiver.recv().await; - return; - } - } - - let prefix = format!("{}-IDLE", self.name); - let do_fake_idle = match self.imap.connect_configured(context).await { - Ok(()) => { - if !self.imap.can_idle() { - true // we have to do fake_idle - } else { - let watch_folder = self.get_watch_folder(context).await; - info!(context, "{} started...", prefix); - let res = self.imap.idle(context, watch_folder).await; - info!(context, "{} ended...", prefix); - if let Err(err) = res { - warn!(context, "{} failed: {} -> reconnecting", prefix, err); - // something is borked, let's start afresh on the next occassion - self.imap.disconnect(context).await; - } - false - } - } - Err(err) => { - info!(context, "{}-IDLE connection fail: {:?}", self.name, err); - // if the connection fails, use fake_idle to retry periodically - // fake_idle() will be woken up by interrupt_idle() as - // well so will act on maybe_network events - true - } - }; - if do_fake_idle { - let watch_folder = self.get_watch_folder(context).await; - self.imap.fake_idle(context, watch_folder).await; - } - - self.state.lock().await.using_handle = false; - } -} diff --git a/src/lib.rs b/src/lib.rs index 3d2dd376c..0012e2fdc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -54,7 +54,6 @@ pub mod imex; mod scheduler; #[macro_use] pub mod job; -mod job_thread; pub mod key; mod keyring; pub mod location; diff --git a/src/scheduler.rs b/src/scheduler.rs index 63ebedf7e..f1cf39974 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -217,14 +217,6 @@ impl Scheduler { _ => false, } } - - /// Check if the scheduler is stoppd. - pub fn is_stopped(&self) -> bool { - match self { - Scheduler::Stopped => true, - _ => false, - } - } } /// Connection state logic shared between imap and smtp connections. diff --git a/src/smtp/mod.rs b/src/smtp/mod.rs index 5491c763a..0bb516b97 100644 --- a/src/smtp/mod.rs +++ b/src/smtp/mod.rs @@ -2,14 +2,13 @@ pub mod send; -use async_std::sync::{channel, Receiver, RwLock, Sender}; use std::time::{Duration, Instant}; use async_smtp::smtp::client::net::*; use async_smtp::*; use crate::constants::*; -use crate::context::{Context, PerformJobsNeeded}; +use crate::context::Context; use crate::events::Event; use crate::login_param::{dc_build_tls, LoginParam}; use crate::oauth2::*; From 563b550f0886dae28be6f892971b540c66510a3d Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 18 Mar 2020 01:50:19 +0100 Subject: [PATCH 015/118] integrate imex and imap --- examples/simple.rs | 20 +++++-- src/configure/mod.rs | 2 +- src/context.rs | 2 +- src/imap/idle.rs | 140 +++++++++++++++++++++++-------------------- src/imap/mod.rs | 3 +- src/imex.rs | 46 +++++++------- src/scheduler.rs | 65 ++++++++++++++++---- 7 files changed, 167 insertions(+), 111 deletions(-) diff --git a/examples/simple.rs b/examples/simple.rs index 56b836b03..82303f932 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -1,6 +1,7 @@ extern crate deltachat; -use async_std::sync::{Arc, RwLock}; +use async_std::task; + use std::time; use tempfile::tempdir; @@ -11,7 +12,7 @@ use deltachat::contact::*; use deltachat::context::*; use deltachat::Event; -fn cb(_ctx: &Context, event: Event) { +fn cb(event: Event) { print!("[{:?}]", event); match event { @@ -39,7 +40,16 @@ async fn main() { let duration = time::Duration::from_millis(4000); println!("info: {:#?}", info); - ctx.run().await; + let ctx1 = ctx.clone(); + task::spawn(async move { + loop { + if let Ok(event) = ctx1.get_next_event() { + cb(event); + } else { + task::sleep(time::Duration::from_millis(100)).await; + } + } + }); println!("configuring"); let args = std::env::args().collect::>(); @@ -51,9 +61,11 @@ async fn main() { ctx.set_config(config::Config::MailPw, Some(&pw)) .await .unwrap(); + ctx.configure().await; - async_std::task::sleep(duration).await; + println!("------ RUN ------"); + ctx.run().await; println!("sending a message"); let contact_id = Contact::create(&ctx, "dignifiedquire", "dignifiedquire@gmail.com") diff --git a/src/configure/mod.rs b/src/configure/mod.rs index b7a896af9..daaee05dc 100644 --- a/src/configure/mod.rs +++ b/src/configure/mod.rs @@ -36,7 +36,7 @@ impl Context { self.sql.get_raw_config_bool(self, "configured").await } - /// Starts a configuration job. + /// Configures this account with the currently set parameters. pub async fn configure(&self) { if self.has_ongoing().await { warn!(self, "There is already another ongoing process running.",); diff --git a/src/context.rs b/src/context.rs index 1127f5509..52a722e12 100644 --- a/src/context.rs +++ b/src/context.rs @@ -79,7 +79,7 @@ pub fn get_info() -> HashMap<&'static str, String> { impl Context { /// Creates new context. pub async fn new(os_name: String, dbfile: PathBuf) -> Result { - pretty_env_logger::try_init_timed().ok(); + // pretty_env_logger::try_init_timed().ok(); let mut blob_fname = OsString::new(); blob_fname.push(dbfile.file_name().unwrap_or_default()); diff --git a/src/imap/idle.rs b/src/imap/idle.rs index 89a410879..21a0b46c5 100644 --- a/src/imap/idle.rs +++ b/src/imap/idle.rs @@ -64,6 +64,8 @@ impl Imap { } pub async fn idle(&mut self, context: &Context, watch_folder: Option) -> Result<()> { + use futures::future::FutureExt; + if !self.can_idle() { return Err(Error::IdleAbilityMissing); } @@ -76,6 +78,7 @@ impl Imap { let session = self.session.take(); let timeout = Duration::from_secs(23 * 60); + if let Some(session) = session { match session.idle() { // BEWARE: If you change the Secure branch you @@ -86,17 +89,24 @@ impl Imap { } let (idle_wait, interrupt) = handle.wait_with_timeout(timeout); - self.interrupt = Some(interrupt); if self.skip_next_idle_wait { // interrupt_idle has happened before we // provided self.interrupt self.skip_next_idle_wait = false; - std::mem::drop(idle_wait); + drop(idle_wait); + drop(interrupt); + info!(context, "Idle wait was skipped"); } else { info!(context, "Idle entering wait-on-remote state"); - match idle_wait.await { + let fut = idle_wait.race( + self.idle_interrupt + .recv() + .map(|_| Ok(IdleResponse::ManualInterrupt)), + ); + + match fut.await { Ok(IdleResponse::NewData(_)) => { info!(context, "Idle has NewData"); } @@ -114,9 +124,12 @@ impl Imap { } } } + // if we can't properly terminate the idle // protocol let's break the connection. - let res = async_std::future::timeout(Duration::from_secs(15), handle.done()) + let res = handle + .done() + .timeout(Duration::from_secs(15)) .await .map_err(|err| { self.trigger_reconnect(); @@ -142,17 +155,23 @@ impl Imap { } let (idle_wait, interrupt) = handle.wait_with_timeout(timeout); - self.interrupt = Some(interrupt); if self.skip_next_idle_wait { // interrupt_idle has happened before we // provided self.interrupt self.skip_next_idle_wait = false; - std::mem::drop(idle_wait); + drop(idle_wait); + drop(interrupt); info!(context, "Idle wait was skipped"); } else { info!(context, "Idle entering wait-on-remote state"); - match idle_wait.await { + let fut = idle_wait.race( + self.idle_interrupt + .recv() + .map(|_| Ok(IdleResponse::ManualInterrupt)), + ); + + match fut.await { Ok(IdleResponse::NewData(_)) => { info!(context, "Idle has NewData"); } @@ -172,7 +191,9 @@ impl Imap { } // if we can't properly terminate the idle // protocol let's break the connection. - let res = async_std::future::timeout(Duration::from_secs(15), handle.done()) + let res = handle + .done() + .timeout(Duration::from_secs(15)) .await .map_err(|err| { self.trigger_reconnect(); @@ -203,58 +224,66 @@ impl Imap { // in this case, we're waiting for a configure job (and an interrupt). let fake_idle_start_time = SystemTime::now(); - info!(context, "IMAP-fake-IDLEing..."); - let interrupt = stop_token::StopSource::new(); - - // check every minute if there are new messages - // TODO: grow sleep durations / make them more flexible - let interval = async_std::stream::interval(Duration::from_secs(60)); - let mut interrupt_interval = interrupt.stop_token().stop_stream(interval); - self.interrupt = Some(interrupt); if self.skip_next_idle_wait { // interrupt_idle has happened before we // provided self.interrupt self.skip_next_idle_wait = false; info!(context, "fake-idle wait was skipped"); } else { - // loop until we are interrupted or if we fetched something - while let Some(_) = interrupt_interval.next().await { - // try to connect with proper login params - // (setup_handle_if_needed might not know about them if we - // never successfully connected) - if let Err(err) = self.connect_configured(context).await { - warn!(context, "fake_idle: could not connect: {}", err); - continue; - } - if self.config.can_idle { - // we only fake-idled because network was gone during IDLE, probably - break; - } - info!(context, "fake_idle is connected"); - // we are connected, let's see if fetching messages results - // in anything. If so, we behave as if IDLE had data but - // will have already fetched the messages so perform_*_fetch - // will not find any new. + // check every minute if there are new messages + // TODO: grow sleep durations / make them more flexible + let mut interval = async_std::stream::interval(Duration::from_secs(60)); - if let Some(ref watch_folder) = watch_folder { - match self.fetch_new_messages(context, watch_folder).await { - Ok(res) => { - info!(context, "fetch_new_messages returned {:?}", res); - if res { - break; + // loop until we are interrupted or if we fetched something + loop { + use futures::future::FutureExt; + match interval + .next() + .race(self.idle_interrupt.recv().map(|_| None)) + .await + { + Some(_) => { + // try to connect with proper login params + // (setup_handle_if_needed might not know about them if we + // never successfully connected) + if let Err(err) = self.connect_configured(context).await { + warn!(context, "fake_idle: could not connect: {}", err); + continue; + } + if self.config.can_idle { + // we only fake-idled because network was gone during IDLE, probably + break; + } + info!(context, "fake_idle is connected"); + // we are connected, let's see if fetching messages results + // in anything. If so, we behave as if IDLE had data but + // will have already fetched the messages so perform_*_fetch + // will not find any new. + + if let Some(ref watch_folder) = watch_folder { + match self.fetch_new_messages(context, watch_folder).await { + Ok(res) => { + info!(context, "fetch_new_messages returned {:?}", res); + if res { + break; + } + } + Err(err) => { + error!(context, "could not fetch from folder: {}", err); + self.trigger_reconnect() + } } } - Err(err) => { - error!(context, "could not fetch from folder: {}", err); - self.trigger_reconnect() - } + } + None => { + // Interrupt + break; } } } } - self.interrupt.take(); info!( context, @@ -266,25 +295,4 @@ impl Imap { / 1000., ); } - - pub async fn interrupt_idle(&mut self, context: &Context) { - let mut interrupt: Option = self.interrupt.take(); - if interrupt.is_none() { - // idle wait is not running, signal it needs to skip - self.skip_next_idle_wait = false; - - // meanwhile idle-wait may have produced the StopSource - interrupt = self.interrupt.take(); - } - // let's manually drop the StopSource - if interrupt.is_some() { - // the imap thread provided us a stop token but might - // not have entered idle_wait yet, give it some time - // for that to happen. XXX handle this without extra wait - // https://github.com/deltachat/deltachat-core-rust/issues/925 - async_std::task::sleep(Duration::from_millis(200)).await; - info!(context, "low-level: dropping stop-source to interrupt idle"); - std::mem::drop(interrupt) - } - } } diff --git a/src/imap/mod.rs b/src/imap/mod.rs index 307d12fbf..111b65567 100644 --- a/src/imap/mod.rs +++ b/src/imap/mod.rs @@ -391,8 +391,7 @@ impl Imap { } } - /// tries connecting to imap account using the specific login - /// parameters + /// Tries connecting to imap account using the specific login parameters. pub async fn connect(&mut self, context: &Context, lp: &LoginParam) -> bool { if lp.mail_server.is_empty() || lp.mail_user.is_empty() || lp.mail_pw.is_empty() { return false; diff --git a/src/imex.rs b/src/imex.rs index 528db3c46..119dff4b9 100644 --- a/src/imex.rs +++ b/src/imex.rs @@ -4,7 +4,6 @@ use std::cmp::{max, min}; use async_std::path::{Path, PathBuf}; use async_std::prelude::*; -use num_traits::FromPrimitive; use rand::{thread_rng, Rng}; use crate::blob::BlobObject; @@ -17,7 +16,6 @@ use crate::dc_tools::*; use crate::e2ee; use crate::error::*; use crate::events::Event; -use crate::job::Job; use crate::key::{self, Key}; use crate::message::{Message, MsgId}; use crate::mimeparser::SystemMessage; @@ -70,15 +68,14 @@ pub enum ImexMode { /// /// Only one import-/export-progress can run at the same time. /// To cancel an import-/export-progress, use dc_stop_ongoing_process(). -pub async fn imex(context: &Context, what: ImexMode, param1: Option>) { - let mut param = Params::new(); - param.set_int(Param::Cmd, what as i32); - if let Some(param1) = param1 { - param.set(Param::Arg, param1.as_ref().to_string_lossy()); - } +pub async fn imex( + context: &Context, + what: ImexMode, + param1: Option>, +) -> Result<()> { + job_imex_imap(context, what, param1).await?; - // job::kill_action(context, Action::ImexImap).await; - // job::add(context, Action::ImexImap, 0, param, 0).await; + Ok(()) } /// Returns the filename of the backup found (otherwise an error) @@ -369,34 +366,35 @@ pub fn normalize_setup_code(s: &str) -> String { out } -pub async fn job_imex_imap(context: &Context, job: &Job) -> Result<()> { +pub async fn job_imex_imap( + context: &Context, + what: ImexMode, + param: Option>, +) -> Result<()> { ensure!(context.alloc_ongoing().await, "could not allocate ongoing"); - let what: Option = job.param.get_int(Param::Cmd).and_then(ImexMode::from_i32); - let param = job.param.get(Param::Arg).unwrap_or_default(); + ensure!(!param.is_some(), "No Import/export dir/file given."); - ensure!(!param.is_empty(), "No Import/export dir/file given."); info!(context, "Import/export process started."); context.call_cb(Event::ImexProgress(10)); ensure!(context.sql.is_open().await, "Database not opened."); - if what == Some(ImexMode::ExportBackup) || what == Some(ImexMode::ExportSelfKeys) { + + let path = param.unwrap(); + if what == ImexMode::ExportBackup || what == ImexMode::ExportSelfKeys { // before we export anything, make sure the private key exists if e2ee::ensure_secret_key_exists(context).await.is_err() { context.free_ongoing().await; bail!("Cannot create private key or private key not available."); } else { - dc_create_folder(context, ¶m).await?; + dc_create_folder(context, &path).await?; } } - let path = Path::new(param); + let success = match what { - Some(ImexMode::ExportSelfKeys) => export_self_keys(context, path).await, - Some(ImexMode::ImportSelfKeys) => import_self_keys(context, path).await, - Some(ImexMode::ExportBackup) => export_backup(context, path).await, - Some(ImexMode::ImportBackup) => import_backup(context, path).await, - None => { - bail!("unknown IMEX type"); - } + ImexMode::ExportSelfKeys => export_self_keys(context, path).await, + ImexMode::ImportSelfKeys => import_self_keys(context, path).await, + ImexMode::ExportBackup => export_backup(context, path).await, + ImexMode::ImportBackup => import_backup(context, path).await, }; context.free_ongoing().await; match success { diff --git a/src/scheduler.rs b/src/scheduler.rs index f1cf39974..4f6bc249f 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -5,6 +5,7 @@ use async_std::task; use std::time::Duration; use crate::context::Context; +use crate::error::Error; use crate::imap::Imap; use crate::job::{self, Thread}; use crate::smtp::Smtp; @@ -64,6 +65,7 @@ impl Scheduler { let ctx1 = ctx.clone(); task::spawn(async move { + info!(ctx1, "starting inbox loop"); let ImapConnectionHandlers { mut connection, stop_receiver, @@ -71,6 +73,8 @@ impl Scheduler { } = inbox_handlers; let fut = async move { + connection.connect_configured(&ctx1).await.unwrap(); + loop { // TODO: correct value let probe_network = false; @@ -87,11 +91,32 @@ impl Scheduler { .await; } Ok(None) | Err(async_std::future::TimeoutError { .. }) => { + let watch_folder = + get_watch_folder(&ctx1, "configured_inbox_folder") + .await + .ok_or_else(|| { + Error::WatchFolderNotFound("not-set".to_string()) + }) + .unwrap(); + // fetch - connection.fetch(&ctx1, "TODO").await; + connection.fetch(&ctx1, &watch_folder).await.unwrap_or_else( + |err| { + error!(ctx1, "{}", err); + }, + ); // idle - connection.idle(&ctx1, Some("TODO".into())).await; + if connection.can_idle() { + connection + .idle(&ctx1, Some(watch_folder)) + .await + .unwrap_or_else(|err| { + error!(ctx1, "{}", err); + }); + } else { + connection.fake_idle(&ctx1, Some(watch_folder)).await; + } } } } @@ -107,6 +132,7 @@ impl Scheduler { let ctx1 = ctx.clone(); task::spawn(async move { + info!(ctx1, "starting smtp loop"); let SmtpConnectionHandlers { mut connection, stop_receiver, @@ -145,6 +171,8 @@ impl Scheduler { fut.race(stop_receiver.recv()).await; shutdown_sender.send(()).await; }); + + info!(ctx, "scheduler is running"); } Scheduler::Running { .. } => { // TODO: return an error @@ -153,38 +181,31 @@ impl Scheduler { } } - fn inbox(&self) -> Option<&ImapConnectionState> { - match self { - Scheduler::Running { ref inbox, .. } => Some(inbox), - _ => None, - } - } - async fn interrupt_inbox(&self) { match self { Scheduler::Running { ref inbox, .. } => inbox.interrupt().await, - _ => panic!("interrupt_imap must be called in running mode"), + _ => {} } } async fn interrupt_mvbox(&self) { match self { Scheduler::Running { ref mvbox, .. } => mvbox.interrupt().await, - _ => panic!("interrupt_mvbox must be called in running mode"), + _ => {} } } async fn interrupt_sentbox(&self) { match self { Scheduler::Running { ref sentbox, .. } => sentbox.interrupt().await, - _ => panic!("interrupt_sentbox must be called in running mode"), + _ => {} } } async fn interrupt_smtp(&self) { match self { Scheduler::Running { ref smtp, .. } => smtp.interrupt().await, - _ => panic!("interrupt_smtp must be called in running mode"), + _ => {} } } @@ -338,3 +359,21 @@ struct ImapConnectionHandlers { stop_receiver: Receiver<()>, shutdown_sender: Sender<()>, } + +async fn get_watch_folder(context: &Context, config_name: impl AsRef) -> Option { + match context + .sql + .get_raw_config(context, config_name.as_ref()) + .await + { + Some(name) => Some(name), + None => { + if config_name.as_ref() == "configured_inbox_folder" { + // initialized with old version, so has not set configured_inbox_folder + Some("INBOX".to_string()) + } else { + None + } + } + } +} From 94c6a01420fade764f3e5c8f69da8ba2369ec7eb Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 18 Mar 2020 02:23:22 +0100 Subject: [PATCH 016/118] some cleanup --- examples/simple.rs | 3 +- src/context.rs | 7 +- src/scheduler.rs | 232 +++++++++++++++++++++------------------------ 3 files changed, 117 insertions(+), 125 deletions(-) diff --git a/examples/simple.rs b/examples/simple.rs index 82303f932..109201c9b 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -67,7 +67,8 @@ async fn main() { println!("------ RUN ------"); ctx.run().await; - println!("sending a message"); + println!("--- SENDING A MESSAGE ---"); + let contact_id = Contact::create(&ctx, "dignifiedquire", "dignifiedquire@gmail.com") .await .unwrap(); diff --git a/src/context.rs b/src/context.rs index 52a722e12..213329630 100644 --- a/src/context.rs +++ b/src/context.rs @@ -128,7 +128,12 @@ impl Context { } pub async fn run(&self) { - self.inner.scheduler.write().await.run(self.clone()).await + let ctx = self.clone(); + println!("RUN LOCK START"); + let l = &mut *self.inner.scheduler.write().await; + println!("RUN LOCK AQ"); + l.run(ctx); + println!("RUN LOCK DONE"); } pub async fn stop(&self) { diff --git a/src/scheduler.rs b/src/scheduler.rs index 4f6bc249f..0f85589ce 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -40,22 +40,116 @@ impl Context { } } +async fn inbox_loop(ctx: Context, inbox_handlers: ImapConnectionHandlers) { + info!(ctx, "starting inbox loop"); + let ImapConnectionHandlers { + mut connection, + stop_receiver, + shutdown_sender, + } = inbox_handlers; + + let fut = async move { + connection.connect_configured(&ctx).await.unwrap(); + + loop { + // TODO: correct value + let probe_network = false; + match job::load_next(&ctx, Thread::Imap, probe_network) + .timeout(Duration::from_millis(200)) + .await + { + Ok(Some(job)) => { + job::perform_job(&ctx, job::Connection::Inbox(&mut connection), job).await; + } + Ok(None) | Err(async_std::future::TimeoutError { .. }) => { + let watch_folder = get_watch_folder(&ctx, "configured_inbox_folder") + .await + .ok_or_else(|| Error::WatchFolderNotFound("not-set".to_string())) + .unwrap(); + + // fetch + connection + .fetch(&ctx, &watch_folder) + .await + .unwrap_or_else(|err| { + error!(ctx, "{}", err); + }); + + // idle + if connection.can_idle() { + connection + .idle(&ctx, Some(watch_folder)) + .await + .unwrap_or_else(|err| { + error!(ctx, "{}", err); + }); + } else { + connection.fake_idle(&ctx, Some(watch_folder)).await; + } + } + } + } + }; + + fut.race(stop_receiver.recv()).await; + shutdown_sender.send(()).await; +} + +async fn smtp_loop(ctx: Context, smtp_handlers: SmtpConnectionHandlers) { + info!(ctx, "starting smtp loop"); + let SmtpConnectionHandlers { + mut connection, + stop_receiver, + shutdown_sender, + idle_interrupt_receiver, + } = smtp_handlers; + + let fut = async move { + loop { + // TODO: correct value + let probe_network = false; + match job::load_next(&ctx, Thread::Smtp, probe_network) + .timeout(Duration::from_millis(200)) + .await + { + Ok(Some(job)) => { + job::perform_job(&ctx, job::Connection::Smtp(&mut connection), job).await; + } + Ok(None) | Err(async_std::future::TimeoutError { .. }) => { + use futures::future::FutureExt; + + // Fake Idle + async_std::task::sleep(Duration::from_millis(500)) + .race(idle_interrupt_receiver.recv().map(|_| ())) + .await; + } + } + } + }; + + fut.race(stop_receiver.recv()).await; + shutdown_sender.send(()).await; +} + impl Scheduler { /// Start the scheduler, panics if it is already running. - pub async fn run(&mut self, ctx: Context) { + pub fn run(&mut self, ctx: Context) { match self { Scheduler::Stopped => { - let ( - ( - ((inbox, inbox_handlers), (mvbox, mvbox_handlers)), - (sentbox, sentbox_handlers), - ), - (smtp, smtp_handlers), - ) = ImapConnectionState::new() - .join(ImapConnectionState::new()) - .join(ImapConnectionState::new()) - .join(SmtpConnectionState::new()) - .await; + let (mvbox, mvbox_handlers) = ImapConnectionState::new(); + let (sentbox, sentbox_handlers) = ImapConnectionState::new(); + let (smtp, smtp_handlers) = SmtpConnectionState::new(); + let (inbox, inbox_handlers) = ImapConnectionState::new(); + + let ctx1 = ctx.clone(); + let _ = task::spawn(async move { inbox_loop(ctx1, inbox_handlers).await }); + + // TODO: mvbox + // TODO: sentbox + + let ctx1 = ctx.clone(); + let _ = task::spawn(async move { smtp_loop(ctx1, smtp_handlers).await }); + *self = Scheduler::Running { inbox, mvbox, @@ -63,116 +157,8 @@ impl Scheduler { smtp, }; - let ctx1 = ctx.clone(); - task::spawn(async move { - info!(ctx1, "starting inbox loop"); - let ImapConnectionHandlers { - mut connection, - stop_receiver, - shutdown_sender, - } = inbox_handlers; - - let fut = async move { - connection.connect_configured(&ctx1).await.unwrap(); - - loop { - // TODO: correct value - let probe_network = false; - match job::load_next(&ctx1, Thread::Imap, probe_network) - .timeout(Duration::from_millis(200)) - .await - { - Ok(Some(job)) => { - job::perform_job( - &ctx1, - job::Connection::Inbox(&mut connection), - job, - ) - .await; - } - Ok(None) | Err(async_std::future::TimeoutError { .. }) => { - let watch_folder = - get_watch_folder(&ctx1, "configured_inbox_folder") - .await - .ok_or_else(|| { - Error::WatchFolderNotFound("not-set".to_string()) - }) - .unwrap(); - - // fetch - connection.fetch(&ctx1, &watch_folder).await.unwrap_or_else( - |err| { - error!(ctx1, "{}", err); - }, - ); - - // idle - if connection.can_idle() { - connection - .idle(&ctx1, Some(watch_folder)) - .await - .unwrap_or_else(|err| { - error!(ctx1, "{}", err); - }); - } else { - connection.fake_idle(&ctx1, Some(watch_folder)).await; - } - } - } - } - }; - - fut.race(stop_receiver.recv()).await; - shutdown_sender.send(()).await; - }); - - // TODO: mvbox - - // TODO: sentbox - - let ctx1 = ctx.clone(); - task::spawn(async move { - info!(ctx1, "starting smtp loop"); - let SmtpConnectionHandlers { - mut connection, - stop_receiver, - shutdown_sender, - idle_interrupt_receiver, - } = smtp_handlers; - - let fut = async move { - loop { - // TODO: correct value - let probe_network = false; - match job::load_next(&ctx1, Thread::Smtp, probe_network) - .timeout(Duration::from_millis(200)) - .await - { - Ok(Some(job)) => { - job::perform_job( - &ctx1, - job::Connection::Smtp(&mut connection), - job, - ) - .await; - } - Ok(None) | Err(async_std::future::TimeoutError { .. }) => { - use futures::future::FutureExt; - - // Fake Idle - async_std::task::sleep(Duration::from_millis(500)) - .race(idle_interrupt_receiver.recv().map(|_| ())) - .await; - } - } - } - }; - - fut.race(stop_receiver.recv()).await; - shutdown_sender.send(()).await; - }); - info!(ctx, "scheduler is running"); + println!("RUN DONE"); } Scheduler::Running { .. } => { // TODO: return an error @@ -271,7 +257,7 @@ pub(crate) struct SmtpConnectionState { } impl SmtpConnectionState { - async fn new() -> (Self, SmtpConnectionHandlers) { + fn new() -> (Self, SmtpConnectionHandlers) { let (stop_sender, stop_receiver) = channel(1); let (shutdown_sender, shutdown_receiver) = channel(1); let (idle_interrupt_sender, idle_interrupt_receiver) = channel(1); @@ -320,7 +306,7 @@ pub(crate) struct ImapConnectionState { impl ImapConnectionState { /// Construct a new connection. - async fn new() -> (Self, ImapConnectionHandlers) { + fn new() -> (Self, ImapConnectionHandlers) { let (stop_sender, stop_receiver) = channel(1); let (idle_interrupt_sender, idle_interrupt_receiver) = channel(1); let (shutdown_sender, shutdown_receiver) = channel(1); From f85b14a7f72f2503816560e26fb3dfb0799007e7 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 18 Mar 2020 15:11:36 +0100 Subject: [PATCH 017/118] the basics work --- examples/simple.rs | 17 +- src/configure/mod.rs | 823 +++++++++++++++++++++---------------------- src/context.rs | 28 +- src/imap/mod.rs | 2 +- src/imex.rs | 15 +- src/job.rs | 41 +-- src/scheduler.rs | 99 ++++-- src/sql.rs | 12 +- 8 files changed, 504 insertions(+), 533 deletions(-) diff --git a/examples/simple.rs b/examples/simple.rs index 109201c9b..113064419 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -1,7 +1,5 @@ extern crate deltachat; -use async_std::task; - use std::time; use tempfile::tempdir; @@ -41,13 +39,9 @@ async fn main() { println!("info: {:#?}", info); let ctx1 = ctx.clone(); - task::spawn(async move { - loop { - if let Ok(event) = ctx1.get_next_event() { - cb(event); - } else { - task::sleep(time::Duration::from_millis(100)).await; - } + std::thread::spawn(move || loop { + if let Ok(event) = ctx1.get_next_event() { + cb(event); } }); @@ -62,11 +56,10 @@ async fn main() { .await .unwrap(); - ctx.configure().await; + ctx.configure().await.unwrap(); println!("------ RUN ------"); - ctx.run().await; - + ctx.clone().run().await; println!("--- SENDING A MESSAGE ---"); let contact_id = Contact::create(&ctx, "dignifiedquire", "dignifiedquire@gmail.com") diff --git a/src/configure/mod.rs b/src/configure/mod.rs index daaee05dc..837ce5648 100644 --- a/src/configure/mod.rs +++ b/src/configure/mod.rs @@ -10,6 +10,7 @@ use crate::config::Config; use crate::constants::*; use crate::context::Context; use crate::dc_tools::*; +use crate::error::{Error, Result}; use crate::imap::Imap; use crate::login_param::{CertificateChecks, LoginParam}; use crate::oauth2::*; @@ -37,31 +38,28 @@ impl Context { } /// Configures this account with the currently set parameters. - pub async fn configure(&self) { - if self.has_ongoing().await { - warn!(self, "There is already another ongoing process running.",); - return; - } + pub async fn configure(&self) -> Result<()> { + ensure!( + !self.has_ongoing().await, + "There is already another ongoing process running." + ); + ensure!( + !self.scheduler.read().await.is_running(), + "Can not configure, already running" + ); + ensure!( + self.sql.is_open().await, + "Cannot configure, database not opened." + ); + ensure!( + self.alloc_ongoing().await, + "Cannot allocate ongoing process" + ); - if self.scheduler.read().await.is_running() { - warn!(self, "Can not configure, already running"); - return; - } - - if !self.sql.is_open().await { - error!(self, "Cannot configure, database not opened.",); - progress!(self, 0); - return; - } - if !self.alloc_ongoing().await { - error!(self, "Cannot allocate ongoing process"); - progress!(self, 0); - return; - } let mut success = false; let mut param_autoconfig: Option = None; - info!(self, "Configure ...",); + info!(self, "Configure ..."); // Variables that are shared between steps: let mut param = LoginParam::from_database(self, "").await; @@ -72,9 +70,6 @@ impl Context { "Internal Error: this value should never be used".to_owned(); let mut keep_flags = 0; - const STEP_12_USE_AUTOCONFIG: u8 = 12; - const STEP_13_AFTER_AUTOCONFIG: u8 = 13; - let mut step_counter: u8 = 0; let (_s, r) = async_std::sync::channel(1); let mut imap = Imap::new(r); @@ -83,349 +78,38 @@ impl Context { while !self.shall_stop_ongoing().await { step_counter += 1; - let success = match step_counter { - // Read login parameters from the database - 1 => { - progress!(self, 1); - if param.addr.is_empty() { - error!(self, "Please enter an email address.",); - } - !param.addr.is_empty() - } - // Step 1: Load the parameters and check email-address and password - 2 => { - if 0 != param.server_flags & DC_LP_AUTH_OAUTH2 { - // the used oauth2 addr may differ, check this. - // if dc_get_oauth2_addr() is not available in the oauth2 implementation, - // just use the given one. - progress!(self, 10); - if let Some(oauth2_addr) = - dc_get_oauth2_addr(self, ¶m.addr, ¶m.mail_pw) - .await - .and_then(|e| e.parse().ok()) - { - info!(self, "Authorized address is {}", oauth2_addr); - param.addr = oauth2_addr; - self.sql - .set_raw_config(self, "addr", Some(param.addr.as_str())) - .await - .ok(); - } - progress!(self, 20); - } - true // no oauth? - just continue it's no error - } - 3 => { - if let Ok(parsed) = param.addr.parse() { - let parsed: EmailAddress = parsed; - param_domain = parsed.domain; - param_addr_urlencoded = - utf8_percent_encode(¶m.addr, NON_ALPHANUMERIC).to_string(); - true - } else { - error!(self, "Bad email-address."); - false + match exec_step( + self, + &mut imap, + &mut is_imap_connected, + &mut param, + &mut param_domain, + &mut param_autoconfig, + &mut param_addr_urlencoded, + &mut keep_flags, + &mut step_counter, + ) + .await + { + Ok(step) => { + success = true; + match step { + Step::Continue => {} + Step::Done => break, } } - // Step 2: Autoconfig - 4 => { - progress!(self, 200); - - if param.mail_server.is_empty() - && param.mail_port == 0 - /*&¶m.mail_user.is_empty() -- the user can enter a loginname which is used by autoconfig then */ - && param.send_server.is_empty() - && param.send_port == 0 - && param.send_user.is_empty() - /*&¶m.send_pw.is_empty() -- the password cannot be auto-configured and is no criterion for autoconfig or not */ - && (param.server_flags & !DC_LP_AUTH_OAUTH2) == 0 - { - // no advanced parameters entered by the user: query provider-database or do Autoconfig - keep_flags = param.server_flags & DC_LP_AUTH_OAUTH2; - if let Some(new_param) = get_offline_autoconfig(self, ¶m) { - // got parameters from our provider-database, skip Autoconfig, preserve the OAuth2 setting - param_autoconfig = Some(new_param); - step_counter = STEP_12_USE_AUTOCONFIG - 1; // minus one as step_counter is increased on next loop - } - } else { - // advanced parameters entered by the user: skip Autoconfig - step_counter = STEP_13_AFTER_AUTOCONFIG - 1; // minus one as step_counter is increased on next loop - } - true - } - /* A. Search configurations from the domain used in the email-address, prefer encrypted */ - 5 => { - if param_autoconfig.is_none() { - let url = format!( - "https://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}", - param_domain, param_addr_urlencoded - ); - param_autoconfig = moz_autoconfigure(self, &url, ¶m).ok(); - } - true - } - 6 => { - progress!(self, 300); - if param_autoconfig.is_none() { - // the doc does not mention `emailaddress=`, however, Thunderbird adds it, see https://releases.mozilla.org/pub/thunderbird/ , which makes some sense - let url = format!( - "https://{}/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress={}", - param_domain, param_addr_urlencoded - ); - param_autoconfig = moz_autoconfigure(self, &url, ¶m).ok(); - } - true - } - /* Outlook section start ------------- */ - /* Outlook uses always SSL but different domains (this comment describes the next two steps) */ - 7 => { - progress!(self, 310); - if param_autoconfig.is_none() { - let url = format!("https://{}/autodiscover/autodiscover.xml", param_domain); - param_autoconfig = outlk_autodiscover(self, &url, ¶m).ok(); - } - true - } - 8 => { - progress!(self, 320); - if param_autoconfig.is_none() { - let url = format!( - "https://{}{}/autodiscover/autodiscover.xml", - "autodiscover.", param_domain - ); - param_autoconfig = outlk_autodiscover(self, &url, ¶m).ok(); - } - true - } - /* ----------- Outlook section end */ - 9 => { - progress!(self, 330); - if param_autoconfig.is_none() { - let url = format!( - "http://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}", - param_domain, param_addr_urlencoded - ); - param_autoconfig = moz_autoconfigure(self, &url, ¶m).ok(); - } - true - } - 10 => { - progress!(self, 340); - if param_autoconfig.is_none() { - // do not transfer the email-address unencrypted - let url = format!( - "http://{}/.well-known/autoconfig/mail/config-v1.1.xml", - param_domain - ); - param_autoconfig = moz_autoconfigure(self, &url, ¶m).ok(); - } - true - } - /* B. If we have no configuration yet, search configuration in Thunderbird's centeral database */ - 11 => { - progress!(self, 350); - if param_autoconfig.is_none() { - /* always SSL for Thunderbird's database */ - let url = - format!("https://autoconfig.thunderbird.net/v1.1/{}", param_domain); - param_autoconfig = moz_autoconfigure(self, &url, ¶m).ok(); - } - true - } - /* C. Do we have any autoconfig result? - If you change the match-number here, also update STEP_12_COPY_AUTOCONFIG above - */ - STEP_12_USE_AUTOCONFIG => { - progress!(self, 500); - if let Some(ref cfg) = param_autoconfig { - info!(self, "Got autoconfig: {}", &cfg); - if !cfg.mail_user.is_empty() { - param.mail_user = cfg.mail_user.clone(); - } - param.mail_server = cfg.mail_server.clone(); /* all other values are always NULL when entering autoconfig */ - param.mail_port = cfg.mail_port; - param.send_server = cfg.send_server.clone(); - param.send_port = cfg.send_port; - param.send_user = cfg.send_user.clone(); - param.server_flags = cfg.server_flags; - /* although param_autoconfig's data are no longer needed from, - it is used to later to prevent trying variations of port/server/logins */ - } - param.server_flags |= keep_flags; - true - } - // Step 3: Fill missing fields with defaults - // If you change the match-number here, also update STEP_13_AFTER_AUTOCONFIG above - STEP_13_AFTER_AUTOCONFIG => { - if param.mail_server.is_empty() { - param.mail_server = format!("imap.{}", param_domain,) - } - if param.mail_port == 0 { - param.mail_port = if 0 != param.server_flags & (0x100 | 0x400) { - 143 - } else { - 993 - } - } - if param.mail_user.is_empty() { - param.mail_user = param.addr.clone(); - } - if param.send_server.is_empty() && !param.mail_server.is_empty() { - param.send_server = param.mail_server.clone(); - if param.send_server.starts_with("imap.") { - param.send_server = param.send_server.replacen("imap", "smtp", 1); - } - } - if param.send_port == 0 { - param.send_port = - if 0 != param.server_flags & DC_LP_SMTP_SOCKET_STARTTLS as i32 { - 587 - } else if 0 != param.server_flags & DC_LP_SMTP_SOCKET_PLAIN as i32 { - 25 - } else { - 465 - } - } - if param.send_user.is_empty() && !param.mail_user.is_empty() { - param.send_user = param.mail_user.clone(); - } - if param.send_pw.is_empty() && !param.mail_pw.is_empty() { - param.send_pw = param.mail_pw.clone() - } - if !dc_exactly_one_bit_set(param.server_flags & DC_LP_AUTH_FLAGS as i32) { - param.server_flags &= !(DC_LP_AUTH_FLAGS as i32); - param.server_flags |= DC_LP_AUTH_NORMAL as i32 - } - if !dc_exactly_one_bit_set(param.server_flags & DC_LP_IMAP_SOCKET_FLAGS as i32) - { - param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS as i32); - param.server_flags |= if param.send_port == 143 { - DC_LP_IMAP_SOCKET_STARTTLS as i32 - } else { - DC_LP_IMAP_SOCKET_SSL as i32 - } - } - if !dc_exactly_one_bit_set( - param.server_flags & (DC_LP_SMTP_SOCKET_FLAGS as i32), - ) { - param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32); - param.server_flags |= if param.send_port == 587 { - DC_LP_SMTP_SOCKET_STARTTLS as i32 - } else if param.send_port == 25 { - DC_LP_SMTP_SOCKET_PLAIN as i32 - } else { - DC_LP_SMTP_SOCKET_SSL as i32 - } - } - /* do we have a complete configuration? */ - if param.mail_server.is_empty() - || param.mail_port == 0 - || param.mail_user.is_empty() - || param.mail_pw.is_empty() - || param.send_server.is_empty() - || param.send_port == 0 - || param.send_user.is_empty() - || param.send_pw.is_empty() - || param.server_flags == 0 - { - error!(self, "Account settings incomplete."); - false - } else { - true - } - } - 14 => { - progress!(self, 600); - /* try to connect to IMAP - if we did not got an autoconfig, - do some further tries with different settings and username variations */ - is_imap_connected = try_imap_connections( - self, - &mut param, - param_autoconfig.is_some(), - &mut imap, - ) - .await; - is_imap_connected - } - 15 => { - progress!(self, 800); - try_smtp_connections(self, &mut param, param_autoconfig.is_some()).await - } - 16 => { - progress!(self, 900); - let create_mvbox = self.get_config_bool(Config::MvboxWatch).await - || self.get_config_bool(Config::MvboxMove).await; - if let Err(err) = imap.ensure_configured_folders(self, create_mvbox).await { - warn!(self, "configuring folders failed: {:?}", err); - false - } else { - let res = imap.select_with_uidvalidity(self, "INBOX").await; - if let Err(err) = res { - error!(self, "could not read INBOX status: {:?}", err); - false - } else { - true - } - } - } - 17 => { - progress!(self, 910); - /* configuration success - write back the configured parameters with the "configured_" prefix; also write the "configured"-flag */ - param - .save_to_database( - self, - "configured_", /*the trailing underscore is correct*/ - ) - .await - .ok(); - - self.sql - .set_raw_config_bool(self, "configured", true) - .await - .ok(); - true - } - 18 => { - progress!(self, 920); - // we generate the keypair just now - we could also postpone this until the first message is sent, however, - // this may result in a unexpected and annoying delay when the user sends his very first message - // (~30 seconds on a Moto G4 play) and might looks as if message sending is always that slow. - success = e2ee::ensure_secret_key_exists(self).await.is_ok(); - info!(self, "key generation completed"); - progress!(self, 940); - break; // We are done here - } - _ => { - error!(self, "Internal error: step counter out of bound",); + Err(err) => { + error!(self, "{}", err); + success = false; break; } - }; - - if !success { - break; } } + if is_imap_connected { imap.disconnect(self).await; } - // remember the entered parameters on success - // and restore to last-entered on failure. - // this way, the parameters visible to the ui are always in-sync with the current configuration. - if success { - LoginParam::from_database(self, "") - .await - .save_to_database(self, "configured_raw_") - .await - .ok(); - } else { - LoginParam::from_database(self, "configured_raw_") - .await - .save_to_database(self, "") - .await - .ok(); - } - if let Some(provider) = provider::get_provider_info(¶m.addr) { if !provider.after_login_hint.is_empty() { let mut msg = Message::new(Viewtype::Text); @@ -439,11 +123,334 @@ impl Context { } } - self.free_ongoing().await; - progress!(self, if success { 1000 } else { 0 }); + // remember the entered parameters on success + // and restore to last-entered on failure. + // this way, the parameters visible to the ui are always in-sync with the current configuration. + if success { + assert!(self.is_configured().await, "epic fail"); + LoginParam::from_database(self, "") + .await + .save_to_database(self, "configured_raw_") + .await + .ok(); + + self.free_ongoing().await; + + progress!(self, 1000); + Ok(()) + } else { + LoginParam::from_database(self, "configured_raw_") + .await + .save_to_database(self, "") + .await + .ok(); + + self.free_ongoing().await; + + progress!(self, 0); + Err(Error::Message("Configure failed".to_string())) + } } } +async fn exec_step( + ctx: &Context, + imap: &mut Imap, + is_imap_connected: &mut bool, + param: &mut LoginParam, + param_domain: &mut String, + param_autoconfig: &mut Option, + param_addr_urlencoded: &mut String, + keep_flags: &mut i32, + step_counter: &mut u8, +) -> Result { + const STEP_12_USE_AUTOCONFIG: u8 = 12; + const STEP_13_AFTER_AUTOCONFIG: u8 = 13; + + match *step_counter { + // Read login parameters from the database + 1 => { + progress!(ctx, 1); + ensure!(!param.addr.is_empty(), "Please enter an email address."); + } + // Step 1: Load the parameters and check email-address and password + 2 => { + if 0 != param.server_flags & DC_LP_AUTH_OAUTH2 { + // the used oauth2 addr may differ, check this. + // if dc_get_oauth2_addr() is not available in the oauth2 implementation, + // just use the given one. + progress!(ctx, 10); + if let Some(oauth2_addr) = dc_get_oauth2_addr(ctx, ¶m.addr, ¶m.mail_pw) + .await + .and_then(|e| e.parse().ok()) + { + info!(ctx, "Authorized address is {}", oauth2_addr); + param.addr = oauth2_addr; + ctx.sql + .set_raw_config(ctx, "addr", Some(param.addr.as_str())) + .await?; + } + progress!(ctx, 20); + } + // no oauth? - just continue it's no error + } + 3 => { + if let Ok(parsed) = param.addr.parse() { + let parsed: EmailAddress = parsed; + *param_domain = parsed.domain; + *param_addr_urlencoded = + utf8_percent_encode(¶m.addr, NON_ALPHANUMERIC).to_string(); + } else { + bail!("Bad email-address."); + } + } + // Step 2: Autoconfig + 4 => { + progress!(ctx, 200); + + if param.mail_server.is_empty() + && param.mail_port == 0 + /* && param.mail_user.is_empty() -- the user can enter a loginname which is used by autoconfig then */ + && param.send_server.is_empty() + && param.send_port == 0 + && param.send_user.is_empty() + /* && param.send_pw.is_empty() -- the password cannot be auto-configured and is no criterion for autoconfig or not */ + && (param.server_flags & !DC_LP_AUTH_OAUTH2) == 0 + { + // no advanced parameters entered by the user: query provider-database or do Autoconfig + *keep_flags = param.server_flags & DC_LP_AUTH_OAUTH2; + if let Some(new_param) = get_offline_autoconfig(ctx, ¶m) { + // got parameters from our provider-database, skip Autoconfig, preserve the OAuth2 setting + *param_autoconfig = Some(new_param); + *step_counter = STEP_12_USE_AUTOCONFIG - 1; // minus one as step_counter is increased on next loop + } + } else { + // advanced parameters entered by the user: skip Autoconfig + *step_counter = STEP_13_AFTER_AUTOCONFIG - 1; // minus one as step_counter is increased on next loop + } + } + /* A. Search configurations from the domain used in the email-address, prefer encrypted */ + 5 => { + if param_autoconfig.is_none() { + let url = format!( + "https://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}", + param_domain, param_addr_urlencoded + ); + *param_autoconfig = moz_autoconfigure(ctx, &url, ¶m).ok(); + } + } + 6 => { + progress!(ctx, 300); + if param_autoconfig.is_none() { + // the doc does not mention `emailaddress=`, however, Thunderbird adds it, see https://releases.mozilla.org/pub/thunderbird/ , which makes some sense + let url = format!( + "https://{}/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress={}", + param_domain, param_addr_urlencoded + ); + *param_autoconfig = moz_autoconfigure(ctx, &url, ¶m).ok(); + } + } + /* Outlook section start ------------- */ + /* Outlook uses always SSL but different domains (this comment describes the next two steps) */ + 7 => { + progress!(ctx, 310); + if param_autoconfig.is_none() { + let url = format!("https://{}/autodiscover/autodiscover.xml", param_domain); + *param_autoconfig = outlk_autodiscover(ctx, &url, ¶m).ok(); + } + } + 8 => { + progress!(ctx, 320); + if param_autoconfig.is_none() { + let url = format!( + "https://{}{}/autodiscover/autodiscover.xml", + "autodiscover.", param_domain + ); + *param_autoconfig = outlk_autodiscover(ctx, &url, ¶m).ok(); + } + } + /* ----------- Outlook section end */ + 9 => { + progress!(ctx, 330); + if param_autoconfig.is_none() { + let url = format!( + "http://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}", + param_domain, param_addr_urlencoded + ); + *param_autoconfig = moz_autoconfigure(ctx, &url, ¶m).ok(); + } + } + 10 => { + progress!(ctx, 340); + if param_autoconfig.is_none() { + // do not transfer the email-address unencrypted + let url = format!( + "http://{}/.well-known/autoconfig/mail/config-v1.1.xml", + param_domain + ); + *param_autoconfig = moz_autoconfigure(ctx, &url, ¶m).ok(); + } + } + /* B. If we have no configuration yet, search configuration in Thunderbird's centeral database */ + 11 => { + progress!(ctx, 350); + if param_autoconfig.is_none() { + /* always SSL for Thunderbird's database */ + let url = format!("https://autoconfig.thunderbird.net/v1.1/{}", param_domain); + *param_autoconfig = moz_autoconfigure(ctx, &url, ¶m).ok(); + } + } + /* C. Do we have any autoconfig result? + If you change the match-number here, also update STEP_12_COPY_AUTOCONFIG above + */ + STEP_12_USE_AUTOCONFIG => { + progress!(ctx, 500); + if let Some(ref cfg) = param_autoconfig { + info!(ctx, "Got autoconfig: {}", &cfg); + if !cfg.mail_user.is_empty() { + param.mail_user = cfg.mail_user.clone(); + } + param.mail_server = cfg.mail_server.clone(); /* all other values are always NULL when entering autoconfig */ + param.mail_port = cfg.mail_port; + param.send_server = cfg.send_server.clone(); + param.send_port = cfg.send_port; + param.send_user = cfg.send_user.clone(); + param.server_flags = cfg.server_flags; + /* although param_autoconfig's data are no longer needed from, + it is used to later to prevent trying variations of port/server/logins */ + } + param.server_flags |= *keep_flags; + } + // Step 3: Fill missing fields with defaults + // If you change the match-number here, also update STEP_13_AFTER_AUTOCONFIG above + STEP_13_AFTER_AUTOCONFIG => { + if param.mail_server.is_empty() { + param.mail_server = format!("imap.{}", param_domain,) + } + if param.mail_port == 0 { + param.mail_port = if 0 != param.server_flags & (0x100 | 0x400) { + 143 + } else { + 993 + } + } + if param.mail_user.is_empty() { + param.mail_user = param.addr.clone(); + } + if param.send_server.is_empty() && !param.mail_server.is_empty() { + param.send_server = param.mail_server.clone(); + if param.send_server.starts_with("imap.") { + param.send_server = param.send_server.replacen("imap", "smtp", 1); + } + } + if param.send_port == 0 { + param.send_port = if 0 != param.server_flags & DC_LP_SMTP_SOCKET_STARTTLS as i32 { + 587 + } else if 0 != param.server_flags & DC_LP_SMTP_SOCKET_PLAIN as i32 { + 25 + } else { + 465 + } + } + if param.send_user.is_empty() && !param.mail_user.is_empty() { + param.send_user = param.mail_user.clone(); + } + if param.send_pw.is_empty() && !param.mail_pw.is_empty() { + param.send_pw = param.mail_pw.clone() + } + if !dc_exactly_one_bit_set(param.server_flags & DC_LP_AUTH_FLAGS as i32) { + param.server_flags &= !(DC_LP_AUTH_FLAGS as i32); + param.server_flags |= DC_LP_AUTH_NORMAL as i32 + } + if !dc_exactly_one_bit_set(param.server_flags & DC_LP_IMAP_SOCKET_FLAGS as i32) { + param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS as i32); + param.server_flags |= if param.send_port == 143 { + DC_LP_IMAP_SOCKET_STARTTLS as i32 + } else { + DC_LP_IMAP_SOCKET_SSL as i32 + } + } + if !dc_exactly_one_bit_set(param.server_flags & (DC_LP_SMTP_SOCKET_FLAGS as i32)) { + param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32); + param.server_flags |= if param.send_port == 587 { + DC_LP_SMTP_SOCKET_STARTTLS as i32 + } else if param.send_port == 25 { + DC_LP_SMTP_SOCKET_PLAIN as i32 + } else { + DC_LP_SMTP_SOCKET_SSL as i32 + } + } + /* do we have a complete configuration? */ + if param.mail_server.is_empty() + || param.mail_port == 0 + || param.mail_user.is_empty() + || param.mail_pw.is_empty() + || param.send_server.is_empty() + || param.send_port == 0 + || param.send_user.is_empty() + || param.send_pw.is_empty() + || param.server_flags == 0 + { + bail!("Account settings incomplete."); + } + } + 14 => { + progress!(ctx, 600); + /* try to connect to IMAP - if we did not got an autoconfig, + do some further tries with different settings and username variations */ + try_imap_connections(ctx, param, param_autoconfig.is_some(), imap).await?; + *is_imap_connected = true; + } + 15 => { + progress!(ctx, 800); + try_smtp_connections(ctx, param, param_autoconfig.is_some()).await?; + } + 16 => { + progress!(ctx, 900); + let create_mvbox = ctx.get_config_bool(Config::MvboxWatch).await + || ctx.get_config_bool(Config::MvboxMove).await; + if let Err(err) = imap.ensure_configured_folders(ctx, create_mvbox).await { + bail!("configuring folders failed: {:?}", err); + } + + if let Err(err) = imap.select_with_uidvalidity(ctx, "INBOX").await { + bail!("could not read INBOX status: {:?}", err); + } + } + 17 => { + progress!(ctx, 910); + // configuration success - write back the configured parameters with the + // "configured_" prefix; also write the "configured"-flag */ + // the trailing underscore is correct + param.save_to_database(ctx, "configured_").await?; + println!("storing configured val"); + ctx.sql.set_raw_config_bool(ctx, "configured", true).await?; + println!("stored configured val"); + } + 18 => { + progress!(ctx, 920); + // we generate the keypair just now - we could also postpone this until the first message is sent, however, + // this may result in a unexpected and annoying delay when the user sends his very first message + // (~30 seconds on a Moto G4 play) and might looks as if message sending is always that slow. + e2ee::ensure_secret_key_exists(ctx).await?; + info!(ctx, "key generation completed"); + progress!(ctx, 940); + return Ok(Step::Done); + } + _ => { + bail!("Internal error: step counter out of bound"); + } + } + + Ok(Step::Continue) +} + +#[derive(Debug)] +enum Step { + Done, + Continue, +} + #[allow(clippy::unnecessary_unwrap)] fn get_offline_autoconfig(context: &Context, param: &LoginParam) -> Option { info!( @@ -507,10 +514,13 @@ async fn try_imap_connections( mut param: &mut LoginParam, was_autoconfig: bool, imap: &mut Imap, -) -> bool { +) -> Result<()> { // progress 650 and 660 - if let Some(res) = try_imap_connection(context, &mut param, was_autoconfig, 0, imap).await { - return res; + if try_imap_connection(context, &mut param, was_autoconfig, 0, imap) + .await + .is_ok() + { + return Ok(()); } progress!(context, 670); param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS); @@ -524,11 +534,9 @@ async fn try_imap_connections( param.send_user = param.send_user.split_at(at).0.to_string(); } // progress 680 and 690 - if let Some(res) = try_imap_connection(context, &mut param, was_autoconfig, 1, imap).await { - res - } else { - false - } + try_imap_connection(context, &mut param, was_autoconfig, 1, imap).await?; + + Ok(()) } async fn try_imap_connection( @@ -537,18 +545,18 @@ async fn try_imap_connection( was_autoconfig: bool, variation: usize, imap: &mut Imap, -) -> Option { - if let Some(res) = try_imap_one_param(context, ¶m, imap).await { - return Some(res); +) -> Result<()> { + if try_imap_one_param(context, ¶m, imap).await.is_ok() { + return Ok(()); } if was_autoconfig { - return Some(false); + bail!("autoconfig"); } progress!(context, 650 + variation * 30); param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS); param.server_flags |= DC_LP_IMAP_SOCKET_STARTTLS; - if let Some(res) = try_imap_one_param(context, ¶m, imap).await { - return Some(res); + if try_imap_one_param(context, ¶m, imap).await.is_ok() { + return Ok(()); } progress!(context, 660 + variation * 30); @@ -557,11 +565,7 @@ async fn try_imap_connection( try_imap_one_param(context, ¶m, imap).await } -async fn try_imap_one_param( - context: &Context, - param: &LoginParam, - imap: &mut Imap, -) -> Option { +async fn try_imap_one_param(context: &Context, param: &LoginParam, imap: &mut Imap) -> Result<()> { let inf = format!( "imap: {}@{}:{} flags=0x{:x} certificate_checks={}", param.mail_user, @@ -571,78 +575,59 @@ async fn try_imap_one_param( param.imap_certificate_checks ); info!(context, "Trying: {}", inf); + if imap.connect(context, ¶m).await { info!(context, "success: {}", inf); - return Some(true); + return Ok(()); } - if context.shall_stop_ongoing().await { - return Some(false); - } - info!(context, "Could not connect: {}", inf); - None + + bail!("Could not connect: {}", inf); } async fn try_smtp_connections( context: &Context, mut param: &mut LoginParam, was_autoconfig: bool, -) -> bool { +) -> Result<()> { let mut smtp = Smtp::new(); /* try to connect to SMTP - if we did not got an autoconfig, the first try was SSL-465 and we do a second try with STARTTLS-587 */ - if let Some(res) = try_smtp_one_param(context, ¶m, &mut smtp).await { - return res; + if try_smtp_one_param(context, ¶m, &mut smtp).await.is_ok() { + return Ok(()); } if was_autoconfig { - return false; + bail!("autoconfig"); } progress!(context, 850); param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32); param.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32; param.send_port = 587; - if let Some(res) = try_smtp_one_param(context, ¶m, &mut smtp).await { - if res { - smtp.disconnect().await; - } - return res; + if try_smtp_one_param(context, ¶m, &mut smtp).await.is_ok() { + return Ok(()); } progress!(context, 860); param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32); param.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32; param.send_port = 25; - if let Some(res) = try_smtp_one_param(context, ¶m, &mut smtp).await { - if res { - smtp.disconnect().await; - } - return res; - } - false + try_smtp_one_param(context, ¶m, &mut smtp).await?; + + Ok(()) } -async fn try_smtp_one_param( - context: &Context, - param: &LoginParam, - smtp: &mut Smtp, -) -> Option { +async fn try_smtp_one_param(context: &Context, param: &LoginParam, smtp: &mut Smtp) -> Result<()> { let inf = format!( "smtp: {}@{}:{} flags: 0x{:x}", param.send_user, param.send_server, param.send_port, param.server_flags ); info!(context, "Trying: {}", inf); - match smtp.connect(context, ¶m).await { - Ok(()) => { - info!(context, "success: {}", inf); - Some(true) - } - Err(err) => { - if context.shall_stop_ongoing().await { - Some(false) - } else { - warn!(context, "could not connect: {}", err); - None - } - } + + if let Err(err) = smtp.connect(context, ¶m).await { + bail!("could not connect: {}", err); } + + info!(context, "success: {}", inf); + smtp.disconnect().await; + Ok(()) } #[cfg(test)] @@ -663,7 +648,7 @@ mod tests { .set_config(Config::MailPw, Some("123456")) .await .unwrap(); - t.ctx.configure().await; + assert!(t.ctx.configure().await.is_err()); } #[async_std::test] diff --git a/src/context.rs b/src/context.rs index 213329630..7c3e5879d 100644 --- a/src/context.rs +++ b/src/context.rs @@ -128,18 +128,16 @@ impl Context { } pub async fn run(&self) { - let ctx = self.clone(); - println!("RUN LOCK START"); - let l = &mut *self.inner.scheduler.write().await; - println!("RUN LOCK AQ"); - l.run(ctx); - println!("RUN LOCK DONE"); + if self.inner.scheduler.read().await.is_running() { + panic!("Already running"); + } + + let scheduler = Scheduler::run(self.clone()); + *self.inner.scheduler.write().await = scheduler; } pub async fn stop(&self) { - if self.inner.scheduler.read().await.is_running() { - self.inner.scheduler.write().await.stop().await; - } + self.inner.stop().await; } /// Returns database file path. @@ -469,11 +467,19 @@ impl Context { } } -impl Drop for Context { +impl InnerContext { + async fn stop(&self) { + if self.scheduler.read().await.is_running() { + self.scheduler.write().await.stop().await; + } + } +} + +impl Drop for InnerContext { fn drop(&mut self) { async_std::task::block_on(async move { self.stop().await; - self.sql.close(self).await; + self.sql.close().await; }); } } diff --git a/src/imap/mod.rs b/src/imap/mod.rs index 111b65567..b1897394b 100644 --- a/src/imap/mod.rs +++ b/src/imap/mod.rs @@ -377,7 +377,7 @@ impl Imap { if self.is_connected() && !self.should_reconnect() { return Ok(()); } - if !context.sql.get_raw_config_bool(context, "configured").await { + if !context.is_configured().await { return Err(Error::ConnectWithoutConfigure); } diff --git a/src/imex.rs b/src/imex.rs index 119dff4b9..6d3a152ac 100644 --- a/src/imex.rs +++ b/src/imex.rs @@ -52,13 +52,10 @@ pub enum ImexMode { } /// Import/export things. -/// For this purpose, the function creates a job that is executed in the IMAP-thread then; -/// this requires to call dc_perform_inbox_jobs() regularly. /// /// What to do is defined by the *what* parameter. /// -/// While dc_imex() returns immediately, the started job may take a while, -/// you can stop it using dc_stop_ongoing_process(). During execution of the job, +/// During execution of the job, /// some events are sent out: /// /// - A number of #DC_EVENT_IMEX_PROGRESS events are sent and may be used to create @@ -67,7 +64,7 @@ pub enum ImexMode { /// - For each file written on export, the function sends #DC_EVENT_IMEX_FILE_WRITTEN /// /// Only one import-/export-progress can run at the same time. -/// To cancel an import-/export-progress, use dc_stop_ongoing_process(). +/// To cancel an import-/export-progress, drop the future returned by this function. pub async fn imex( context: &Context, what: ImexMode, @@ -101,7 +98,7 @@ pub async fn has_backup(context: &Context, dir_name: impl AsRef) -> Result newest_backup_time = curr_backup_time; } info!(context, "backup_time of {} is {}", name, curr_backup_time); - sql.close(&context).await; + sql.close().await; } } } @@ -423,7 +420,7 @@ async fn import_backup(context: &Context, backup_to_import: impl AsRef) -> !context.is_configured().await, "Cannot import backups to accounts in use." ); - context.sql.close(&context).await; + context.sql.close().await; dc_delete_file(context, context.get_dbfile()).await; ensure!( !context.get_dbfile().exists().await, @@ -528,7 +525,7 @@ async fn export_backup(context: &Context, dir: impl AsRef) -> Result<()> { context.sql.execute("VACUUM;", paramsv![]).await.ok(); // we close the database during the copy of the dbfile - context.sql.close(context).await; + context.sql.close().await; info!( context, "Backup '{}' to '{}'.", @@ -568,7 +565,7 @@ async fn export_backup(context: &Context, dir: impl AsRef) -> Result<()> { Ok(()) } }; - dest_sql.close(context).await; + dest_sql.close().await; Ok(res?) } diff --git a/src/job.rs b/src/job.rs index b223c5979..6021d1949 100644 --- a/src/job.rs +++ b/src/job.rs @@ -3,8 +3,8 @@ //! This module implements a job queue maintained in the SQLite database //! and job types. +use std::fmt; use std::future::Future; -use std::{fmt, time}; use deltachat_derive::{FromSql, ToSql}; use itertools::Itertools; @@ -581,45 +581,6 @@ async fn kill_ids(context: &Context, job_ids: &[u32]) -> sql::Result<()> { Ok(()) } -async fn get_next_wakeup_time(context: &Context, thread: Thread) -> time::Duration { - let t: i64 = context - .sql - .query_get_value( - context, - "SELECT MIN(desired_timestamp) FROM jobs WHERE thread=?;", - paramsv![thread], - ) - .await - .unwrap_or_default(); - - let mut wakeup_time = time::Duration::new(10 * 60, 0); - let now = time(); - if t > 0 { - if t > now { - wakeup_time = time::Duration::new((t - now) as u64, 0); - } else { - wakeup_time = time::Duration::new(0, 0); - } - } - - wakeup_time -} - -pub async fn maybe_network(context: &Context) { - unimplemented!(); - // { - // context.smtp.state.write().await.probe_network = true; - // context - // .probe_imap_network - // .store(true, std::sync::atomic::Ordering::Relaxed); - // } - - // interrupt_smtp_idle(context).await; - // interrupt_inbox_idle(context).await; - // interrupt_mvbox_idle(context).await; - // interrupt_sentbox_idle(context).await; -} - pub async fn action_exists(context: &Context, action: Action) -> bool { context .sql diff --git a/src/scheduler.rs b/src/scheduler.rs index 0f85589ce..eb0ae5844 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -19,10 +19,16 @@ pub(crate) enum Scheduler { mvbox: ImapConnectionState, sentbox: ImapConnectionState, smtp: SmtpConnectionState, + probe_network: bool, }, } impl Context { + /// Indicate that the network likely has come back. + pub async fn maybe_network(&self) { + self.scheduler.write().await.maybe_network().await; + } + pub(crate) async fn interrupt_inbox(&self) { self.scheduler.read().await.interrupt_inbox().await; } @@ -52,14 +58,14 @@ async fn inbox_loop(ctx: Context, inbox_handlers: ImapConnectionHandlers) { connection.connect_configured(&ctx).await.unwrap(); loop { - // TODO: correct value - let probe_network = false; + let probe_network = ctx.scheduler.read().await.get_probe_network(); match job::load_next(&ctx, Thread::Imap, probe_network) .timeout(Duration::from_millis(200)) .await { Ok(Some(job)) => { job::perform_job(&ctx, job::Connection::Inbox(&mut connection), job).await; + ctx.scheduler.write().await.set_probe_network(false); } Ok(None) | Err(async_std::future::TimeoutError { .. }) => { let watch_folder = get_watch_folder(&ctx, "configured_inbox_folder") @@ -106,14 +112,14 @@ async fn smtp_loop(ctx: Context, smtp_handlers: SmtpConnectionHandlers) { let fut = async move { loop { - // TODO: correct value - let probe_network = false; + let probe_network = ctx.scheduler.read().await.get_probe_network(); match job::load_next(&ctx, Thread::Smtp, probe_network) .timeout(Duration::from_millis(200)) .await { Ok(Some(job)) => { job::perform_job(&ctx, job::Connection::Smtp(&mut connection), job).await; + ctx.scheduler.write().await.set_probe_network(false); } Ok(None) | Err(async_std::future::TimeoutError { .. }) => { use futures::future::FutureExt; @@ -133,40 +139,65 @@ async fn smtp_loop(ctx: Context, smtp_handlers: SmtpConnectionHandlers) { impl Scheduler { /// Start the scheduler, panics if it is already running. - pub fn run(&mut self, ctx: Context) { + pub fn run(ctx: Context) -> Self { + let (mvbox, mvbox_handlers) = ImapConnectionState::new(); + let (sentbox, sentbox_handlers) = ImapConnectionState::new(); + let (smtp, smtp_handlers) = SmtpConnectionState::new(); + let (inbox, inbox_handlers) = ImapConnectionState::new(); + + let ctx1 = ctx.clone(); + task::spawn(async move { inbox_loop(ctx1, inbox_handlers).await }); + + // TODO: mvbox + // TODO: sentbox + + let ctx1 = ctx.clone(); + task::spawn(async move { smtp_loop(ctx1, smtp_handlers).await }); + + let res = Scheduler::Running { + inbox, + mvbox, + sentbox, + smtp, + probe_network: false, + }; + + info!(ctx, "scheduler is running"); + println!("RUN DONE"); + res + } + + fn set_probe_network(&mut self, val: bool) { match self { - Scheduler::Stopped => { - let (mvbox, mvbox_handlers) = ImapConnectionState::new(); - let (sentbox, sentbox_handlers) = ImapConnectionState::new(); - let (smtp, smtp_handlers) = SmtpConnectionState::new(); - let (inbox, inbox_handlers) = ImapConnectionState::new(); - - let ctx1 = ctx.clone(); - let _ = task::spawn(async move { inbox_loop(ctx1, inbox_handlers).await }); - - // TODO: mvbox - // TODO: sentbox - - let ctx1 = ctx.clone(); - let _ = task::spawn(async move { smtp_loop(ctx1, smtp_handlers).await }); - - *self = Scheduler::Running { - inbox, - mvbox, - sentbox, - smtp, - }; - - info!(ctx, "scheduler is running"); - println!("RUN DONE"); - } - Scheduler::Running { .. } => { - // TODO: return an error - panic!("WARN: already running"); + Scheduler::Running { + ref mut probe_network, + .. + } => { + *probe_network = val; } + _ => panic!("set_probe_network can only be called when running"), } } + fn get_probe_network(&self) -> bool { + match self { + Scheduler::Running { probe_network, .. } => *probe_network, + _ => panic!("get_probe_network can only be called when running"), + } + } + + async fn maybe_network(&mut self) { + if !self.is_running() { + return; + } + self.set_probe_network(true); + self.interrupt_inbox() + .join(self.interrupt_mvbox()) + .join(self.interrupt_sentbox()) + .join(self.interrupt_smtp()) + .await; + } + async fn interrupt_inbox(&self) { match self { Scheduler::Running { ref inbox, .. } => inbox.interrupt().await, @@ -206,6 +237,7 @@ impl Scheduler { mvbox, sentbox, smtp, + .. } => { inbox .stop() @@ -213,6 +245,7 @@ impl Scheduler { .join(sentbox.stop()) .join(smtp.stop()) .await; + *self = Scheduler::Stopped; } } } diff --git a/src/sql.rs b/src/sql.rs index d21f18886..0d25d7757 100644 --- a/src/sql.rs +++ b/src/sql.rs @@ -87,12 +87,10 @@ impl Sql { self.pool.read().await.is_some() } - pub async fn close(&self, context: &Context) { + pub async fn close(&self) { let _ = self.pool.write().await.take(); self.in_use.remove(); // drop closes the connection - - info!(context, "Database closed."); } // return true on success, false on failure @@ -101,7 +99,7 @@ impl Sql { Ok(_) => true, Err(crate::error::Error::SqlError(Error::SqlAlreadyOpen)) => false, Err(_) => { - self.close(context).await; + self.close().await; false } } @@ -374,10 +372,8 @@ impl Sql { pub async fn get_raw_config_bool(&self, context: &Context, key: impl AsRef) -> bool { // Not the most obvious way to encode bool as string, but it is matter // of backward compatibility. - self.get_raw_config_int(context, key) - .await - .unwrap_or_default() - > 0 + let res = self.get_raw_config_int(context, key).await; + res.unwrap_or_default() > 0 } pub async fn set_raw_config_bool(&self, context: &Context, key: T, value: bool) -> Result<()> From ab2cb1ad1f807da167044a334f67d0a74f4dd470 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 18 Mar 2020 15:31:37 +0100 Subject: [PATCH 018/118] add missing loops --- src/scheduler.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++++-- tests/stress.rs | 1 - 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/scheduler.rs b/src/scheduler.rs index eb0ae5844..458e29cb1 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -101,6 +101,53 @@ async fn inbox_loop(ctx: Context, inbox_handlers: ImapConnectionHandlers) { shutdown_sender.send(()).await; } +async fn simple_imap_loop( + ctx: Context, + inbox_handlers: ImapConnectionHandlers, + folder: impl AsRef, +) { + info!(ctx, "starting simple loop"); + let ImapConnectionHandlers { + mut connection, + stop_receiver, + shutdown_sender, + } = inbox_handlers; + + let fut = async move { + connection.connect_configured(&ctx).await.unwrap(); + + loop { + let watch_folder = get_watch_folder(&ctx, folder.as_ref()) + .await + .ok_or_else(|| Error::WatchFolderNotFound("not-set".to_string())) + .unwrap(); + + // fetch + connection + .fetch(&ctx, &watch_folder) + .await + .unwrap_or_else(|err| { + error!(ctx, "{}", err); + }); + + // idle + if connection.can_idle() { + connection + .idle(&ctx, Some(watch_folder)) + .await + .unwrap_or_else(|err| { + error!(ctx, "{}", err); + }); + } else { + connection.fake_idle(&ctx, Some(watch_folder)).await; + } + } + }; + + fut.race(stop_receiver.recv()).await; + shutdown_sender.send(()).await; +} + async fn smtp_loop(ctx: Context, smtp_handlers: SmtpConnectionHandlers) { info!(ctx, "starting smtp loop"); let SmtpConnectionHandlers { @@ -148,8 +195,15 @@ impl Scheduler { let ctx1 = ctx.clone(); task::spawn(async move { inbox_loop(ctx1, inbox_handlers).await }); - // TODO: mvbox - // TODO: sentbox + let ctx1 = ctx.clone(); + task::spawn(async move { + simple_imap_loop(ctx1, mvbox_handlers, "configured_mvbox_folder").await + }); + + let ctx1 = ctx.clone(); + task::spawn(async move { + simple_imap_loop(ctx1, sentbox_handlers, "configured_sentbox_folder").await + }); let ctx1 = ctx.clone(); task::spawn(async move { smtp_loop(ctx1, smtp_handlers).await }); diff --git a/tests/stress.rs b/tests/stress.rs index ed6710fc8..2e70ae799 100644 --- a/tests/stress.rs +++ b/tests/stress.rs @@ -2,7 +2,6 @@ use deltachat::config; use deltachat::context::*; -use deltachat::Event; use tempfile::{tempdir, TempDir}; /* some data used for testing From 9d313b4e0e14e4330b4d309213588daf59ae1065 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 18 Mar 2020 16:29:34 +0100 Subject: [PATCH 019/118] cleanup and fix most of imap --- Cargo.lock | 4 +- Cargo.toml | 5 +- src/imap/client.rs | 140 ++++++++++++++++++----------- src/imap/idle.rs | 208 ++++++++++++-------------------------------- src/imap/session.rs | 176 +++++++------------------------------ 5 files changed, 181 insertions(+), 352 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 86a3774a0..a0dc28f8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -85,6 +85,7 @@ dependencies = [ [[package]] name = "async-imap" version = "0.2.0" +source = "git+https://github.com/async-email/async-imap?branch=feat/send#be7b9cace12d0f323ef1e6e0a9fc698f4e26b64d" dependencies = [ "async-native-tls 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -618,7 +619,7 @@ dependencies = [ name = "deltachat" version = "1.27.0" dependencies = [ - "async-imap 0.2.0", + "async-imap 0.2.0 (git+https://github.com/async-email/async-imap?branch=feat/send)", "async-native-tls 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "async-smtp 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3143,6 +3144,7 @@ dependencies = [ "checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" "checksum ascii_utils 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a" "checksum async-attributes 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "efd3d156917d94862e779f356c5acae312b08fd3121e792c857d7928c8088423" +"checksum async-imap 0.2.0 (git+https://github.com/async-email/async-imap?branch=feat/send)" = "" "checksum async-native-tls 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9e9e7a929bd34c68a82d58a4de7f86fffdaf97fb2af850162a7bb19dd7269b33" "checksum async-smtp 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3652e5c6072c0694a2bcdb7e8409980d2676bd4f024adf4aab10c68fd2b48f5" "checksum async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "538ecb01eb64eecd772087e5b6f7540cbc917f047727339a472dafed2185b267" diff --git a/Cargo.toml b/Cargo.toml index 142f3ecd0..2393bb68d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" license = "MPL-2.0" [profile.release] -lto = true +# lto = true [dependencies] deltachat_derive = { path = "./deltachat_derive" } @@ -23,7 +23,7 @@ num-traits = "0.2.6" async-smtp = "0.2" email = { git = "https://github.com/deltachat/rust-email", branch = "master" } lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" } -async-imap = { path = "../../async-imap"} # "0.2" +async-imap = { git = "https://github.com/async-email/async-imap", branch = "feat/send" } async-native-tls = "0.3.1" async-std = { version = "1.4", features = ["unstable"] } base64 = "0.11" @@ -90,4 +90,3 @@ required-features = ["rustyline"] default = ["nightly"] vendored = ["async-native-tls/vendored", "reqwest/native-tls-vendored", "async-smtp/native-tls-vendored"] nightly = ["pgp/nightly"] - diff --git a/src/imap/client.rs b/src/imap/client.rs index b2674afa4..923bc65ec 100644 --- a/src/imap/client.rs +++ b/src/imap/client.rs @@ -1,20 +1,80 @@ +use std::ops::{Deref, DerefMut}; + use async_imap::{ error::{Error as ImapError, Result as ImapResult}, Client as ImapClient, }; -use async_native_tls::TlsStream; use async_std::net::{self, TcpStream}; use super::session::Session; use crate::login_param::{dc_build_tls, CertificateChecks}; +use super::session::SessionStream; + #[derive(Debug)] -pub(crate) enum Client { - Secure(ImapClient>), - Insecure(ImapClient), +pub(crate) struct Client { + is_secure: bool, + inner: ImapClient>, +} + +impl Deref for Client { + type Target = ImapClient>; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DerefMut for Client { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } } impl Client { + pub async fn login, P: AsRef>( + self, + username: U, + password: P, + ) -> std::result::Result { + let Client { inner, is_secure } = self; + let session = inner + .login(username, password) + .await + .map_err(|(err, client)| { + ( + err, + Client { + is_secure, + inner: client, + }, + ) + })?; + Ok(Session { inner: session }) + } + + pub async fn authenticate>( + self, + auth_type: S, + authenticator: &A, + ) -> std::result::Result { + let Client { inner, is_secure } = self; + let session = + inner + .authenticate(auth_type, authenticator) + .await + .map_err(|(err, client)| { + ( + err, + Client { + is_secure, + inner: client, + }, + ) + })?; + Ok(Session { inner: session }) + } + pub async fn connect_secure>( addr: A, domain: S, @@ -22,7 +82,8 @@ impl Client { ) -> ImapResult { let stream = TcpStream::connect(addr).await?; let tls = dc_build_tls(certificate_checks); - let tls_stream = tls.connect(domain.as_ref(), stream).await?; + let tls_stream: Box = + Box::new(tls.connect(domain.as_ref(), stream).await?); let mut client = ImapClient::new(tls_stream); if std::env::var(crate::DCC_IMAP_DEBUG).is_ok() { client.debug = true; @@ -33,11 +94,14 @@ impl Client { .await .ok_or_else(|| ImapError::Bad("failed to read greeting".to_string()))?; - Ok(Client::Secure(client)) + Ok(Client { + is_secure: true, + inner: client, + }) } pub async fn connect_insecure(addr: A) -> ImapResult { - let stream = TcpStream::connect(addr).await?; + let stream: Box = Box::new(TcpStream::connect(addr).await?); let mut client = ImapClient::new(stream); if std::env::var(crate::DCC_IMAP_DEBUG).is_ok() { @@ -48,57 +112,29 @@ impl Client { .await .ok_or_else(|| ImapError::Bad("failed to read greeting".to_string()))?; - Ok(Client::Insecure(client)) + Ok(Client { + is_secure: false, + inner: client, + }) } pub async fn secure>( self, - domain: S, - certificate_checks: CertificateChecks, + _domain: S, + _certificate_checks: CertificateChecks, ) -> ImapResult { - match self { - Client::Insecure(client) => { - let tls = dc_build_tls(certificate_checks); - let client_sec = client.secure(domain, tls).await?; + if self.is_secure { + Ok(self) + } else { + unimplemented!() + // let Client { inner, .. } = self; + // let tls = dc_build_tls(certificate_checks); + // let client_sec = inner.secure(domain, tls).await?; - Ok(Client::Secure(client_sec)) - } - // Nothing to do - Client::Secure(_) => Ok(self), - } - } - - pub async fn authenticate>( - self, - auth_type: S, - authenticator: &A, - ) -> Result { - match self { - Client::Secure(i) => match i.authenticate(auth_type, authenticator).await { - Ok(session) => Ok(Session::Secure(session)), - Err((err, c)) => Err((err, Client::Secure(c))), - }, - Client::Insecure(i) => match i.authenticate(auth_type, authenticator).await { - Ok(session) => Ok(Session::Insecure(session)), - Err((err, c)) => Err((err, Client::Insecure(c))), - }, - } - } - - pub async fn login, P: AsRef>( - self, - username: U, - password: P, - ) -> Result { - match self { - Client::Secure(i) => match i.login(username, password).await { - Ok(session) => Ok(Session::Secure(session)), - Err((err, c)) => Err((err, Client::Secure(c))), - }, - Client::Insecure(i) => match i.login(username, password).await { - Ok(session) => Ok(Session::Insecure(session)), - Err((err, c)) => Err((err, Client::Insecure(c))), - }, + // Ok(Client { + // is_secure: true, + // inner: client_sec, + // }) } } } diff --git a/src/imap/idle.rs b/src/imap/idle.rs index 21a0b46c5..6982c122c 100644 --- a/src/imap/idle.rs +++ b/src/imap/idle.rs @@ -1,8 +1,6 @@ use super::Imap; -use async_imap::extensions::idle::{Handle as ImapIdleHandle, IdleResponse}; -use async_native_tls::TlsStream; -use async_std::net::TcpStream; +use async_imap::extensions::idle::IdleResponse; use async_std::prelude::*; use std::time::{Duration, SystemTime}; @@ -37,27 +35,6 @@ impl From for Error { } } -#[derive(Debug)] -pub(crate) enum IdleHandle { - Secure(ImapIdleHandle>), - Insecure(ImapIdleHandle), -} - -impl Session { - pub fn idle(self) -> IdleHandle { - match self { - Session::Secure(i) => { - let h = i.idle(); - IdleHandle::Secure(h) - } - Session::Insecure(i) => { - let h = i.idle(); - IdleHandle::Insecure(h) - } - } - } -} - impl Imap { pub fn can_idle(&self) -> bool { self.config.can_idle @@ -80,138 +57,69 @@ impl Imap { let timeout = Duration::from_secs(23 * 60); if let Some(session) = session { - match session.idle() { - // BEWARE: If you change the Secure branch you - // typically also need to change the Insecure branch. - IdleHandle::Secure(mut handle) => { - if let Err(err) = handle.init().await { - return Err(Error::IdleProtocolFailed(err)); + let mut handle = session.idle(); + if let Err(err) = handle.init().await { + return Err(Error::IdleProtocolFailed(err)); + } + + let (idle_wait, interrupt) = handle.wait_with_timeout(timeout); + + if self.skip_next_idle_wait { + // interrupt_idle has happened before we + // provided self.interrupt + self.skip_next_idle_wait = false; + drop(idle_wait); + drop(interrupt); + + info!(context, "Idle wait was skipped"); + } else { + info!(context, "Idle entering wait-on-remote state"); + let fut = idle_wait.race( + self.idle_interrupt + .recv() + .map(|_| Ok(IdleResponse::ManualInterrupt)), + ); + + match fut.await { + Ok(IdleResponse::NewData(_)) => { + info!(context, "Idle has NewData"); } - - let (idle_wait, interrupt) = handle.wait_with_timeout(timeout); - - if self.skip_next_idle_wait { - // interrupt_idle has happened before we - // provided self.interrupt - self.skip_next_idle_wait = false; - drop(idle_wait); - drop(interrupt); - - info!(context, "Idle wait was skipped"); - } else { - info!(context, "Idle entering wait-on-remote state"); - let fut = idle_wait.race( - self.idle_interrupt - .recv() - .map(|_| Ok(IdleResponse::ManualInterrupt)), - ); - - match fut.await { - Ok(IdleResponse::NewData(_)) => { - info!(context, "Idle has NewData"); - } - // TODO: idle_wait does not distinguish manual interrupts - // from Timeouts if we would know it's a Timeout we could bail - // directly and reconnect . - Ok(IdleResponse::Timeout) => { - info!(context, "Idle-wait timeout or interruption"); - } - Ok(IdleResponse::ManualInterrupt) => { - info!(context, "Idle wait was interrupted"); - } - Err(err) => { - warn!(context, "Idle wait errored: {:?}", err); - } - } + // TODO: idle_wait does not distinguish manual interrupts + // from Timeouts if we would know it's a Timeout we could bail + // directly and reconnect . + Ok(IdleResponse::Timeout) => { + info!(context, "Idle-wait timeout or interruption"); } - - // if we can't properly terminate the idle - // protocol let's break the connection. - let res = handle - .done() - .timeout(Duration::from_secs(15)) - .await - .map_err(|err| { - self.trigger_reconnect(); - Error::IdleTimeout(err) - })?; - - match res { - Ok(session) => { - self.session = Some(Session::Secure(session)); - } - Err(err) => { - // if we cannot terminate IDLE it probably - // means that we waited long (with idle_wait) - // but the network went away/changed - self.trigger_reconnect(); - return Err(Error::IdleProtocolFailed(err)); - } + Ok(IdleResponse::ManualInterrupt) => { + info!(context, "Idle wait was interrupted"); + } + Err(err) => { + warn!(context, "Idle wait errored: {:?}", err); } } - IdleHandle::Insecure(mut handle) => { - if let Err(err) = handle.init().await { - return Err(Error::IdleProtocolFailed(err)); - } + } - let (idle_wait, interrupt) = handle.wait_with_timeout(timeout); + // if we can't properly terminate the idle + // protocol let's break the connection. + let res = handle + .done() + .timeout(Duration::from_secs(15)) + .await + .map_err(|err| { + self.trigger_reconnect(); + Error::IdleTimeout(err) + })?; - if self.skip_next_idle_wait { - // interrupt_idle has happened before we - // provided self.interrupt - self.skip_next_idle_wait = false; - drop(idle_wait); - drop(interrupt); - info!(context, "Idle wait was skipped"); - } else { - info!(context, "Idle entering wait-on-remote state"); - let fut = idle_wait.race( - self.idle_interrupt - .recv() - .map(|_| Ok(IdleResponse::ManualInterrupt)), - ); - - match fut.await { - Ok(IdleResponse::NewData(_)) => { - info!(context, "Idle has NewData"); - } - // TODO: idle_wait does not distinguish manual interrupts - // from Timeouts if we would know it's a Timeout we could bail - // directly and reconnect . - Ok(IdleResponse::Timeout) => { - info!(context, "Idle-wait timeout or interruption"); - } - Ok(IdleResponse::ManualInterrupt) => { - info!(context, "Idle wait was interrupted"); - } - Err(err) => { - warn!(context, "Idle wait errored: {:?}", err); - } - } - } - // if we can't properly terminate the idle - // protocol let's break the connection. - let res = handle - .done() - .timeout(Duration::from_secs(15)) - .await - .map_err(|err| { - self.trigger_reconnect(); - Error::IdleTimeout(err) - })?; - - match res { - Ok(session) => { - self.session = Some(Session::Insecure(session)); - } - Err(err) => { - // if we cannot terminate IDLE it probably - // means that we waited long (with idle_wait) - // but the network went away/changed - self.trigger_reconnect(); - return Err(Error::IdleProtocolFailed(err)); - } - } + match res { + Ok(session) => { + self.session = Some(Session { inner: session }); + } + Err(err) => { + // if we cannot terminate IDLE it probably + // means that we waited long (with idle_wait) + // but the network went away/changed + self.trigger_reconnect(); + return Err(Error::IdleProtocolFailed(err)); } } } diff --git a/src/imap/session.rs b/src/imap/session.rs index 9cac58515..8b8a680a3 100644 --- a/src/imap/session.rs +++ b/src/imap/session.rs @@ -1,155 +1,39 @@ -use async_imap::{ - error::Result as ImapResult, - types::{Capabilities, Fetch, Mailbox, Name}, - Session as ImapSession, -}; +use std::ops::{Deref, DerefMut}; + +use async_imap::Session as ImapSession; use async_native_tls::TlsStream; use async_std::net::TcpStream; -use async_std::prelude::*; #[derive(Debug)] -pub(crate) enum Session { - Secure(ImapSession>), - Insecure(ImapSession), +pub(crate) struct Session { + pub(super) inner: ImapSession>, +} + +pub(crate) trait SessionStream: + async_std::io::Read + async_std::io::Write + Unpin + Send + Sync + std::fmt::Debug +{ +} + +impl SessionStream for TlsStream {} +impl SessionStream for TcpStream {} + +impl Deref for Session { + type Target = ImapSession>; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DerefMut for Session { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } } impl Session { - pub async fn capabilities(&mut self) -> ImapResult { - let res = match self { - Session::Secure(i) => i.capabilities().await?, - Session::Insecure(i) => i.capabilities().await?, - }; - - Ok(res) - } - - pub async fn list( - &mut self, - reference_name: Option<&str>, - mailbox_pattern: Option<&str>, - ) -> ImapResult> + '_ + Send + Unpin> { - match self { - Session::Secure(i) => i.list(reference_name, mailbox_pattern).await, - Session::Insecure(_i) => { - unimplemented!() - // i.list(reference_name, mailbox_pattern).await - } - } - } - - pub async fn create>(&mut self, mailbox_name: S) -> ImapResult<()> { - match self { - Session::Secure(i) => i.create(mailbox_name).await?, - Session::Insecure(i) => i.create(mailbox_name).await?, - } - Ok(()) - } - - pub async fn subscribe>(&mut self, mailbox: S) -> ImapResult<()> { - match self { - Session::Secure(i) => i.subscribe(mailbox).await?, - Session::Insecure(i) => i.subscribe(mailbox).await?, - } - Ok(()) - } - - pub async fn close(&mut self) -> ImapResult<()> { - match self { - Session::Secure(i) => i.close().await?, - Session::Insecure(i) => i.close().await?, - } - Ok(()) - } - - pub async fn select>(&mut self, mailbox_name: S) -> ImapResult { - let mbox = match self { - Session::Secure(i) => i.select(mailbox_name).await?, - Session::Insecure(i) => i.select(mailbox_name).await?, - }; - - Ok(mbox) - } - - pub async fn fetch<'a, S1, S2>( - &'a mut self, - sequence_set: S1, - query: S2, - ) -> ImapResult> + 'a + Send + Unpin> - where - S1: 'a + AsRef, - S2: 'a + AsRef, - { - let res = match self { - Session::Secure(i) => i.fetch(sequence_set, query).await?, - Session::Insecure(_i) => { - unimplemented!() - // i.fetch(sequence_set, query).await? - } - }; - Ok(res) - } - - pub async fn uid_fetch<'a, S1, S2>( - &'a mut self, - uid_set: S1, - query: S2, - ) -> ImapResult> + 'a + Send + Unpin> - where - S1: 'a + AsRef, - S2: 'a + AsRef, - { - let res = match self { - Session::Secure(i) => i.uid_fetch(uid_set, query).await?, - Session::Insecure(_i) => { - unimplemented!() - // i.uid_fetch(uid_set, query).await? - } - }; - - Ok(res) - } - - pub async fn uid_store<'a, S1, S2>( - &'a mut self, - uid_set: S1, - query: S2, - ) -> ImapResult> + 'a + Send + Unpin> - where - S1: 'a + AsRef, - S2: 'a + AsRef, - { - let res = match self { - Session::Secure(i) => i.uid_store(uid_set, query).await?, - Session::Insecure(_i) => { - unimplemented!() - // i.uid_store(uid_set, query).await? - } - }; - Ok(res) - } - - pub async fn uid_mv, S2: AsRef>( - &mut self, - uid_set: S1, - mailbox_name: S2, - ) -> ImapResult<()> { - match self { - Session::Secure(i) => i.uid_mv(uid_set, mailbox_name).await?, - Session::Insecure(i) => i.uid_mv(uid_set, mailbox_name).await?, - } - Ok(()) - } - - pub async fn uid_copy, S2: AsRef>( - &mut self, - uid_set: S1, - mailbox_name: S2, - ) -> ImapResult<()> { - match self { - Session::Secure(i) => i.uid_copy(uid_set, mailbox_name).await?, - Session::Insecure(i) => i.uid_copy(uid_set, mailbox_name).await?, - } - - Ok(()) + pub fn idle(self) -> async_imap::extensions::idle::Handle> { + let Session { inner } = self; + inner.idle() } } From 9981e84a42c48c31a748d822467fe1aad701e082 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 18 Mar 2020 16:34:18 +0100 Subject: [PATCH 020/118] avoid race condition on scheduler start --- src/context.rs | 4 ++-- src/scheduler.rs | 19 +++++++++---------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/context.rs b/src/context.rs index 7c3e5879d..f5545c6ab 100644 --- a/src/context.rs +++ b/src/context.rs @@ -132,8 +132,8 @@ impl Context { panic!("Already running"); } - let scheduler = Scheduler::run(self.clone()); - *self.inner.scheduler.write().await = scheduler; + let l = &mut *self.inner.scheduler.write().await; + l.run(self.clone()); } pub async fn stop(&self) { diff --git a/src/scheduler.rs b/src/scheduler.rs index 458e29cb1..7f86d03e4 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -186,12 +186,20 @@ async fn smtp_loop(ctx: Context, smtp_handlers: SmtpConnectionHandlers) { impl Scheduler { /// Start the scheduler, panics if it is already running. - pub fn run(ctx: Context) -> Self { + pub fn run(&mut self, ctx: Context) { let (mvbox, mvbox_handlers) = ImapConnectionState::new(); let (sentbox, sentbox_handlers) = ImapConnectionState::new(); let (smtp, smtp_handlers) = SmtpConnectionState::new(); let (inbox, inbox_handlers) = ImapConnectionState::new(); + *self = Scheduler::Running { + inbox, + mvbox, + sentbox, + smtp, + probe_network: false, + }; + let ctx1 = ctx.clone(); task::spawn(async move { inbox_loop(ctx1, inbox_handlers).await }); @@ -208,17 +216,8 @@ impl Scheduler { let ctx1 = ctx.clone(); task::spawn(async move { smtp_loop(ctx1, smtp_handlers).await }); - let res = Scheduler::Running { - inbox, - mvbox, - sentbox, - smtp, - probe_network: false, - }; - info!(ctx, "scheduler is running"); println!("RUN DONE"); - res } fn set_probe_network(&mut self, val: bool) { From 1846f20f6e80ed5c7d42f3becedcfe396f94a71a Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Sat, 21 Mar 2020 14:24:41 +0100 Subject: [PATCH 021/118] upgrade repl --- Cargo.lock | 11 ++ Cargo.toml | 13 +- README.md | 4 +- examples/repl/cmdline.rs | 407 ++++++++++++++++++++------------------- examples/repl/main.rs | 259 +++++++------------------ src/contact.rs | 4 +- src/context.rs | 12 ++ src/dc_receive_imf.rs | 4 +- src/e2ee.rs | 4 +- src/lib.rs | 17 +- src/log.rs | 8 +- src/mimefactory.rs | 2 +- src/mimeparser.rs | 2 +- src/peerstate.rs | 6 +- src/scheduler.rs | 1 - src/securejoin.rs | 4 +- src/sql.rs | 12 +- 17 files changed, 352 insertions(+), 418 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a0dc28f8b..e7f163ace 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -50,6 +50,14 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "arrayref" version = "0.3.6" @@ -619,6 +627,7 @@ dependencies = [ name = "deltachat" version = "1.27.0" dependencies = [ + "ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", "async-imap 0.2.0 (git+https://github.com/async-email/async-imap?branch=feat/send)", "async-native-tls 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "async-smtp 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -646,6 +655,7 @@ dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "lettre_email 0.9.2 (git+https://github.com/deltachat/lettre)", "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "mailparse 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)", "native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "num-derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3139,6 +3149,7 @@ dependencies = [ "checksum aesni 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2f70a6b5f971e473091ab7cfb5ffac6cde81666c4556751d8d5620ead8abf100" "checksum aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)" = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +"checksum ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" "checksum arrayref 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" "checksum arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" "checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" diff --git a/Cargo.toml b/Cargo.toml index 2393bb68d..ba9d61252 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,12 +57,15 @@ mailparse = "0.10.2" encoded-words = { git = "https://github.com/async-email/encoded-words", branch="master" } native-tls = "0.2.3" image = { version = "0.22.4", default-features=false, features = ["gif_codec", "jpeg", "ico", "png_codec", "pnm", "webp", "bmp"] } -pretty_env_logger = "0.3.1" - -rustyline = { version = "4.1.0", optional = true } futures = "0.3.4" crossbeam-queue = "0.2.1" + +pretty_env_logger = { version = "0.3.1", optional = true } +log = {version = "0.4.8", optional = true } +rustyline = { version = "4.1.0", optional = true } +ansi_term = { version = "0.12.1", optional = true } + [dev-dependencies] tempfile = "3.0" pretty_assertions = "0.6.1" @@ -83,10 +86,12 @@ path = "examples/simple.rs" [[example]] name = "repl" path = "examples/repl/main.rs" -required-features = ["rustyline"] +required-features = ["repl"] [features] default = ["nightly"] +internals = [] +repl = ["internals", "rustyline", "log", "pretty_env_logger", "ansi_term"] vendored = ["async-native-tls/vendored", "reqwest/native-tls-vendored", "async-smtp/native-tls-vendored"] nightly = ["pgp/nightly"] diff --git a/README.md b/README.md index 6b4613b56..50c244b14 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ To download and install the official compiler for the Rust programming language, and the Cargo package manager, run the command in your user environment: ``` -curl https://sh.rustup.rs -sSf | sh +$ curl https://sh.rustup.rs -sSf | sh ``` ## Using the CLI client @@ -17,7 +17,7 @@ curl https://sh.rustup.rs -sSf | sh Compile and run Delta Chat Core command line utility, using `cargo`: ``` -cargo run --example repl -- /path/to/db +$ RUST_LOG=info cargo run --example repl --features repl -- /path/to/db ``` Configure your account (if not already configured): diff --git a/examples/repl/cmdline.rs b/examples/repl/cmdline.rs index b065f5845..7b135aaa4 100644 --- a/examples/repl/cmdline.rs +++ b/examples/repl/cmdline.rs @@ -1,6 +1,7 @@ -use std::path::Path; use std::str::FromStr; +use async_std::path::Path; + use deltachat::chat::{self, Chat, ChatId, ChatVisibility}; use deltachat::chatlist::*; use deltachat::constants::*; @@ -10,7 +11,6 @@ use deltachat::dc_receive_imf::*; use deltachat::dc_tools::*; use deltachat::error::Error; use deltachat::imex::*; -use deltachat::job::*; use deltachat::location; use deltachat::lot::LotState; use deltachat::message::{self, Message, MessageState, MsgId}; @@ -23,63 +23,66 @@ use deltachat::{config, provider}; /// Reset database tables. /// Argument is a bitmask, executing single or multiple actions in one call. /// e.g. bitmask 7 triggers actions definded with bits 1, 2 and 4. -fn dc_reset_tables(context: &Context, bits: i32) -> i32 { +async fn reset_tables(context: &Context, bits: i32) { println!("Resetting tables ({})...", bits); if 0 != bits & 1 { - sql::execute(context, &context.sql, "DELETE FROM jobs;", params![]).unwrap(); + context + .sql() + .execute("DELETE FROM jobs;", paramsv![]) + .await + .unwrap(); println!("(1) Jobs reset."); } if 0 != bits & 2 { - sql::execute( - context, - &context.sql, - "DELETE FROM acpeerstates;", - params![], - ) - .unwrap(); + context + .sql() + .execute("DELETE FROM acpeerstates;", paramsv![]) + .await + .unwrap(); println!("(2) Peerstates reset."); } if 0 != bits & 4 { - sql::execute(context, &context.sql, "DELETE FROM keypairs;", params![]).unwrap(); + context + .sql() + .execute("DELETE FROM keypairs;", paramsv![]) + .await + .unwrap(); println!("(4) Private keypairs reset."); } if 0 != bits & 8 { - sql::execute( - context, - &context.sql, - "DELETE FROM contacts WHERE id>9;", - params![], - ) - .unwrap(); - sql::execute( - context, - &context.sql, - "DELETE FROM chats WHERE id>9;", - params![], - ) - .unwrap(); - sql::execute( - context, - &context.sql, - "DELETE FROM chats_contacts;", - params![], - ) - .unwrap(); - sql::execute( - context, - &context.sql, - "DELETE FROM msgs WHERE id>9;", - params![], - ) - .unwrap(); - sql::execute( - context, - &context.sql, - "DELETE FROM config WHERE keyname LIKE 'imap.%' OR keyname LIKE 'configured%';", - params![], - ) - .unwrap(); - sql::execute(context, &context.sql, "DELETE FROM leftgrps;", params![]).unwrap(); + context + .sql() + .execute("DELETE FROM contacts WHERE id>9;", paramsv![]) + .await + .unwrap(); + context + .sql() + .execute("DELETE FROM chats WHERE id>9;", paramsv![]) + .await + .unwrap(); + context + .sql() + .execute("DELETE FROM chats_contacts;", paramsv![]) + .await + .unwrap(); + context + .sql() + .execute("DELETE FROM msgs WHERE id>9;", paramsv![]) + .await + .unwrap(); + context + .sql() + .execute( + "DELETE FROM config WHERE keyname LIKE 'imap.%' OR keyname LIKE 'configured%';", + paramsv![], + ) + .await + .unwrap(); + context + .sql() + .execute("DELETE FROM leftgrps;", paramsv![]) + .await + .unwrap(); println!("(8) Rest but server config reset."); } @@ -87,14 +90,12 @@ fn dc_reset_tables(context: &Context, bits: i32) -> i32 { chat_id: ChatId::new(0), msg_id: MsgId::new(0), }); - - 1 } -fn dc_poke_eml_file(context: &Context, filename: impl AsRef) -> Result<(), Error> { - let data = dc_read_file(context, filename)?; +async fn poke_eml_file(context: &Context, filename: impl AsRef) -> Result<(), Error> { + let data = dc_read_file(context, filename).await?; - if let Err(err) = dc_receive_imf(context, &data, "import", 0, false) { + if let Err(err) = dc_receive_imf(context, &data, "import", 0, false).await { println!("dc_receive_imf errored: {:?}", err); } Ok(()) @@ -103,38 +104,29 @@ fn dc_poke_eml_file(context: &Context, filename: impl AsRef) -> Result<(), /// Import a file to the database. /// For testing, import a folder with eml-files, a single eml-file, e-mail plus public key and so on. /// For normal importing, use imex(). -/// -/// @private @memberof Context -/// @param context The context as created by dc_context_new(). -/// @param spec The file or directory to import. NULL for the last command. -/// @return 1=success, 0=error. -fn poke_spec(context: &Context, spec: Option<&str>) -> libc::c_int { - if !context.sql.is_open() { - error!(context, "Import: Database not opened."); - return 0; - } - - let mut read_cnt = 0; +async fn poke_spec(context: &Context, spec: Option<&str>) -> bool { + let mut read_cnt: usize = 0; let real_spec: String; - /* if `spec` is given, remember it for later usage; if it is not given, try to use the last one */ + // if `spec` is given, remember it for later usage; if it is not given, try to use the last one if let Some(spec) = spec { real_spec = spec.to_string(); context - .sql + .sql() .set_raw_config(context, "import_spec", Some(&real_spec)) + .await .unwrap(); } else { - let rs = context.sql.get_raw_config(context, "import_spec"); + let rs = context.sql().get_raw_config(context, "import_spec").await; if rs.is_none() { error!(context, "Import: No file or folder given."); - return 0; + return false; } real_spec = rs.unwrap(); } if let Some(suffix) = dc_get_filesuffix_lc(&real_spec) { - if suffix == "eml" && dc_poke_eml_file(context, &real_spec).is_ok() { + if suffix == "eml" && poke_eml_file(context, &real_spec).await.is_ok() { read_cnt += 1 } } else { @@ -143,7 +135,7 @@ fn poke_spec(context: &Context, spec: Option<&str>) -> libc::c_int { let dir = std::fs::read_dir(dir_name); if dir.is_err() { error!(context, "Import: Cannot open directory \"{}\".", &real_spec,); - return 0; + return false; } else { let dir = dir.unwrap(); for entry in dir { @@ -156,7 +148,7 @@ fn poke_spec(context: &Context, spec: Option<&str>) -> libc::c_int { if name.ends_with(".eml") { let path_plus_name = format!("{}/{}", &real_spec, name); println!("Import: {}", path_plus_name); - if dc_poke_eml_file(context, path_plus_name).is_ok() { + if poke_eml_file(context, path_plus_name).await.is_ok() { read_cnt += 1 } } @@ -170,11 +162,14 @@ fn poke_spec(context: &Context, spec: Option<&str>) -> libc::c_int { msg_id: MsgId::new(0), }); } - 1 + true } -fn log_msg(context: &Context, prefix: impl AsRef, msg: &Message) { - let contact = Contact::get_by_id(context, msg.get_from_id()).expect("invalid contact"); +async fn log_msg(context: &Context, prefix: impl AsRef, msg: &Message) { + let contact = Contact::get_by_id(context, msg.get_from_id()) + .await + .expect("invalid contact"); + let contact_name = contact.get_name(); let contact_id = contact.get_id(); @@ -217,7 +212,7 @@ fn log_msg(context: &Context, prefix: impl AsRef, msg: &Message) { ); } -fn log_msglist(context: &Context, msglist: &Vec) -> Result<(), Error> { +async fn log_msglist(context: &Context, msglist: &Vec) -> Result<(), Error> { let mut lines_out = 0; for &msg_id in msglist { if msg_id.is_daymarker() { @@ -233,8 +228,8 @@ fn log_msglist(context: &Context, msglist: &Vec) -> Result<(), Error> { ); lines_out += 1 } - let msg = Message::load_from_db(context, msg_id)?; - log_msg(context, "", &msg); + let msg = Message::load_from_db(context, msg_id).await?; + log_msg(context, "", &msg).await; } } if lines_out > 0 { @@ -245,7 +240,7 @@ fn log_msglist(context: &Context, msglist: &Vec) -> Result<(), Error> { Ok(()) } -fn log_contactlist(context: &Context, contacts: &Vec) { +async fn log_contactlist(context: &Context, contacts: &Vec) { let mut contacts = contacts.clone(); if !contacts.contains(&1) { contacts.push(1); @@ -253,10 +248,10 @@ fn log_contactlist(context: &Context, contacts: &Vec) { for contact_id in contacts { let line; let mut line2 = "".to_string(); - if let Ok(contact) = Contact::get_by_id(context, contact_id) { + if let Ok(contact) = Contact::get_by_id(context, contact_id).await { let name = contact.get_name(); let addr = contact.get_addr(); - let verified_state = contact.is_verified(context); + let verified_state = contact.is_verified(context).await; let verified_str = if VerifiedStatus::Unverified != verified_state { if verified_state == VerifiedStatus::BidirectVerified { " √√" @@ -280,7 +275,7 @@ fn log_contactlist(context: &Context, contacts: &Vec) { "addr unset" } ); - let peerstate = Peerstate::from_addr(context, &context.sql, &addr); + let peerstate = Peerstate::from_addr(context, &addr).await; if peerstate.is_some() && contact_id != 1 as libc::c_uint { line2 = format!( ", prefer-encrypt={}", @@ -297,10 +292,13 @@ fn chat_prefix(chat: &Chat) -> &'static str { chat.typ.into() } -pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { - let chat_id = *context.cmdline_sel_chat_id.read().unwrap(); +pub async fn cmdline( + context: Context, + line: &str, + chat_id: &mut ChatId, +) -> Result<(), failure::Error> { let mut sel_chat = if !chat_id.is_unset() { - Chat::load_from_db(context, chat_id).ok() + Chat::load_from_db(&context, *chat_id).await.ok() } else { None }; @@ -341,7 +339,6 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { configure\n\ connect\n\ disconnect\n\ - interrupt\n\ maybenetwork\n\ housekeeping\n\ help imex (Import/Export)\n\ @@ -403,7 +400,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { =============================================" ), }, - "initiate-key-transfer" => match initiate_key_transfer(context) { + "initiate-key-transfer" => match initiate_key_transfer(&context).await { Ok(setup_code) => println!( "Setup code for the transferred setup message: {}", setup_code, @@ -413,9 +410,9 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { "get-setupcodebegin" => { ensure!(!arg1.is_empty(), "Argument missing."); let msg_id: MsgId = MsgId::new(arg1.parse()?); - let msg = Message::load_from_db(context, msg_id)?; + let msg = Message::load_from_db(&context, msg_id).await?; if msg.is_setupmessage() { - let setupcodebegin = msg.get_setupcodebegin(context); + let setupcodebegin = msg.get_setupcodebegin(&context).await; println!( "The setup code for setup message {} starts with: {}", msg_id, @@ -430,29 +427,29 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { !arg1.is_empty() && !arg2.is_empty(), "Arguments expected" ); - continue_key_transfer(context, MsgId::new(arg1.parse()?), &arg2)?; + continue_key_transfer(&context, MsgId::new(arg1.parse()?), &arg2).await?; } "has-backup" => { - has_backup(context, blobdir)?; + has_backup(&context, blobdir).await?; } "export-backup" => { - imex(context, ImexMode::ExportBackup, Some(blobdir)); + imex(&context, ImexMode::ExportBackup, Some(blobdir)).await?; } "import-backup" => { ensure!(!arg1.is_empty(), "Argument missing."); - imex(context, ImexMode::ImportBackup, Some(arg1)); + imex(&context, ImexMode::ImportBackup, Some(arg1)).await?; } "export-keys" => { - imex(context, ImexMode::ExportSelfKeys, Some(blobdir)); + imex(&context, ImexMode::ExportSelfKeys, Some(blobdir)).await?; } "import-keys" => { - imex(context, ImexMode::ImportSelfKeys, Some(blobdir)); + imex(&context, ImexMode::ImportSelfKeys, Some(blobdir)).await?; } "export-setup" => { - let setup_code = create_setup_code(context); + let setup_code = create_setup_code(&context); let file_name = blobdir.join("autocrypt-setup-message.html"); - let file_content = render_setup_file(context, &setup_code)?; - std::fs::write(&file_name, file_content)?; + let file_content = render_setup_file(&context, &setup_code).await?; + async_std::fs::write(&file_name, file_content).await?; println!( "Setup message written to: {}\nSetup code: {}", file_name.display(), @@ -460,49 +457,47 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { ); } "poke" => { - ensure!(0 != poke_spec(context, Some(arg1)), "Poke failed"); + ensure!(poke_spec(&context, Some(arg1)).await, "Poke failed"); } "reset" => { ensure!(!arg1.is_empty(), "Argument missing: 1=jobs, 2=peerstates, 4=private keys, 8=rest but server config"); let bits: i32 = arg1.parse()?; ensure!(bits < 16, " must be lower than 16."); - ensure!(0 != dc_reset_tables(context, bits), "Reset failed"); + reset_tables(&context, bits).await; } "stop" => { - context.stop_ongoing(); + context.stop_ongoing().await; } "set" => { ensure!(!arg1.is_empty(), "Argument missing."); let key = config::Config::from_str(&arg1)?; let value = if arg2.is_empty() { None } else { Some(arg2) }; - context.set_config(key, value)?; + context.set_config(key, value).await?; } "get" => { ensure!(!arg1.is_empty(), "Argument missing."); let key = config::Config::from_str(&arg1)?; - let val = context.get_config(key); + let val = context.get_config(key).await; println!("{}={:?}", key, val); } "info" => { - println!("{:#?}", context.get_info()); - } - "interrupt" => { - interrupt_inbox_idle(context); + println!("{:#?}", context.get_info().await); } "maybenetwork" => { - maybe_network(context); + context.maybe_network().await; } "housekeeping" => { - sql::housekeeping(context); + sql::housekeeping(&context).await; } "listchats" | "listarchived" | "chats" => { let listflags = if arg0 == "listarchived" { 0x01 } else { 0 }; let chatlist = Chatlist::try_load( - context, + &context, listflags, if arg1.is_empty() { None } else { Some(arg1) }, None, - )?; + ) + .await?; let cnt = chatlist.len(); if cnt > 0 { @@ -511,20 +506,20 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { ); for i in (0..cnt).rev() { - let chat = Chat::load_from_db(context, chatlist.get_chat_id(i))?; + let chat = Chat::load_from_db(&context, chatlist.get_chat_id(i)).await?; println!( "{}#{}: {} [{} fresh] {}", chat_prefix(&chat), chat.get_id(), chat.get_name(), - chat.get_id().get_fresh_msg_cnt(context), + chat.get_id().get_fresh_msg_cnt(&context).await, match chat.visibility { ChatVisibility::Normal => "", ChatVisibility::Archived => "📦", ChatVisibility::Pinned => "📌", }, ); - let lot = chatlist.get_summary(context, i, Some(&chat)); + let lot = chatlist.get_summary(&context, i, Some(&chat)).await; let statestr = if chat.visibility == ChatVisibility::Archived { " [Archived]" } else { @@ -557,7 +552,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { ); } } - if location::is_sending_locations_to_chat(context, ChatId::new(0)) { + if location::is_sending_locations_to_chat(&context, ChatId::new(0)).await { println!("Location streaming enabled."); } println!("{} chats", cnt); @@ -567,21 +562,21 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { bail!("Argument [chat-id] is missing."); } if !arg1.is_empty() { - let chat_id = ChatId::new(arg1.parse()?); - println!("Selecting chat {}", chat_id); - sel_chat = Some(Chat::load_from_db(context, chat_id)?); - *context.cmdline_sel_chat_id.write().unwrap() = chat_id; + let id = ChatId::new(arg1.parse()?); + println!("Selecting chat {}", id); + sel_chat = Some(Chat::load_from_db(&context, id).await?); + *chat_id = id; } ensure!(sel_chat.is_some(), "Failed to select chat"); let sel_chat = sel_chat.as_ref().unwrap(); - let msglist = chat::get_chat_msgs(context, sel_chat.get_id(), 0x1, None); - let members = chat::get_chat_contacts(context, sel_chat.id); + let msglist = chat::get_chat_msgs(&context, sel_chat.get_id(), 0x1, None).await; + let members = chat::get_chat_contacts(&context, sel_chat.id).await; let subtitle = if sel_chat.is_device_talk() { "device-talk".to_string() } else if sel_chat.get_type() == Chattype::Single && !members.is_empty() { - let contact = Contact::get_by_id(context, members[0])?; + let contact = Contact::get_by_id(&context, members[0]).await?; contact.get_addr().to_string() } else { format!("{} member(s)", members.len()) @@ -597,7 +592,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { } else { "" }, - match sel_chat.get_profile_image(context) { + match sel_chat.get_profile_image(&context).await { Some(icon) => match icon.to_str() { Some(icon) => format!(" Icon: {}", icon), _ => " Icon: Err".to_string(), @@ -605,38 +600,42 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { _ => "".to_string(), }, ); - log_msglist(context, &msglist)?; - if let Some(draft) = sel_chat.get_id().get_draft(context)? { - log_msg(context, "Draft", &draft); + log_msglist(&context, &msglist).await?; + if let Some(draft) = sel_chat.get_id().get_draft(&context).await? { + log_msg(&context, "Draft", &draft).await; } - println!("{} messages.", sel_chat.get_id().get_msg_cnt(context)); - chat::marknoticed_chat(context, sel_chat.get_id())?; + println!( + "{} messages.", + sel_chat.get_id().get_msg_cnt(&context).await + ); + chat::marknoticed_chat(&context, sel_chat.get_id()).await?; } "createchat" => { ensure!(!arg1.is_empty(), "Argument missing."); let contact_id: libc::c_int = arg1.parse()?; - let chat_id = chat::create_by_contact_id(context, contact_id as u32)?; + let chat_id = chat::create_by_contact_id(&context, contact_id as u32).await?; println!("Single#{} created successfully.", chat_id,); } "createchatbymsg" => { ensure!(!arg1.is_empty(), "Argument missing"); let msg_id = MsgId::new(arg1.parse()?); - let chat_id = chat::create_by_msg_id(context, msg_id)?; - let chat = Chat::load_from_db(context, chat_id)?; + let chat_id = chat::create_by_msg_id(&context, msg_id).await?; + let chat = Chat::load_from_db(&context, chat_id).await?; println!("{}#{} created successfully.", chat_prefix(&chat), chat_id,); } "creategroup" => { ensure!(!arg1.is_empty(), "Argument missing."); - let chat_id = chat::create_group_chat(context, VerifiedStatus::Unverified, arg1)?; + let chat_id = + chat::create_group_chat(&context, VerifiedStatus::Unverified, arg1).await?; println!("Group#{} created successfully.", chat_id); } "createverified" => { ensure!(!arg1.is_empty(), "Argument missing."); - let chat_id = chat::create_group_chat(context, VerifiedStatus::Verified, arg1)?; + let chat_id = chat::create_group_chat(&context, VerifiedStatus::Verified, arg1).await?; println!("VerifiedGroup#{} created successfully.", chat_id); } @@ -646,10 +645,12 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { let contact_id_0: libc::c_int = arg1.parse()?; if chat::add_contact_to_chat( - context, + &context, sel_chat.as_ref().unwrap().get_id(), contact_id_0 as u32, - ) { + ) + .await + { println!("Contact added to chat."); } else { bail!("Cannot add contact to chat."); @@ -660,17 +661,18 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { ensure!(!arg1.is_empty(), "Argument missing."); let contact_id_1: libc::c_int = arg1.parse()?; chat::remove_contact_from_chat( - context, + &context, sel_chat.as_ref().unwrap().get_id(), contact_id_1 as u32, - )?; + ) + .await?; println!("Contact added to chat."); } "groupname" => { ensure!(sel_chat.is_some(), "No chat selected."); ensure!(!arg1.is_empty(), "Argument missing."); - chat::set_chat_name(context, sel_chat.as_ref().unwrap().get_id(), arg1)?; + chat::set_chat_name(&context, sel_chat.as_ref().unwrap().get_id(), arg1).await?; println!("Chat name set"); } @@ -678,24 +680,27 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { ensure!(sel_chat.is_some(), "No chat selected."); ensure!(!arg1.is_empty(), "Argument missing."); - chat::set_chat_profile_image(context, sel_chat.as_ref().unwrap().get_id(), arg1)?; + chat::set_chat_profile_image(&context, sel_chat.as_ref().unwrap().get_id(), arg1) + .await?; println!("Chat image set"); } "chatinfo" => { ensure!(sel_chat.is_some(), "No chat selected."); - let contacts = chat::get_chat_contacts(context, sel_chat.as_ref().unwrap().get_id()); + let contacts = + chat::get_chat_contacts(&context, sel_chat.as_ref().unwrap().get_id()).await; println!("Memberlist:"); - log_contactlist(context, &contacts); + log_contactlist(&context, &contacts).await; println!( "{} contacts\nLocation streaming: {}", contacts.len(), location::is_sending_locations_to_chat( - context, + &context, sel_chat.as_ref().unwrap().get_id() - ), + ) + .await, ); } "getlocations" => { @@ -703,12 +708,13 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { let contact_id = arg1.parse().unwrap_or_default(); let locations = location::get_range( - context, + &context, sel_chat.as_ref().unwrap().get_id(), contact_id, 0, 0, - ); + ) + .await; let default_marker = "-".to_string(); for location in &locations { let marker = location.marker.as_ref().unwrap_or(&default_marker); @@ -734,7 +740,12 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { ensure!(!arg1.is_empty(), "No timeout given."); let seconds = arg1.parse()?; - location::send_locations_to_chat(context, sel_chat.as_ref().unwrap().get_id(), seconds); + location::send_locations_to_chat( + &context, + sel_chat.as_ref().unwrap().get_id(), + seconds, + ) + .await; println!( "Locations will be sent to Chat#{} for {} seconds. Use 'setlocation ' to play around.", sel_chat.as_ref().unwrap().get_id(), @@ -749,7 +760,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { let latitude = arg1.parse()?; let longitude = arg2.parse()?; - let continue_streaming = location::set(context, latitude, longitude, 0.); + let continue_streaming = location::set(&context, latitude, longitude, 0.).await; if continue_streaming { println!("Success, streaming should be continued."); } else { @@ -757,7 +768,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { } } "dellocations" => { - location::delete_all(context)?; + location::delete_all(&context).await?; } "send" => { ensure!(sel_chat.is_some(), "No chat selected."); @@ -765,11 +776,11 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { let msg = format!("{} {}", arg1, arg2); - chat::send_text_msg(context, sel_chat.as_ref().unwrap().get_id(), msg)?; + chat::send_text_msg(&context, sel_chat.as_ref().unwrap().get_id(), msg).await?; } "sendempty" => { ensure!(sel_chat.is_some(), "No chat selected."); - chat::send_text_msg(context, sel_chat.as_ref().unwrap().get_id(), "".into())?; + chat::send_text_msg(&context, sel_chat.as_ref().unwrap().get_id(), "".into()).await?; } "sendimage" | "sendfile" => { ensure!(sel_chat.is_some(), "No chat selected."); @@ -784,7 +795,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { if !arg2.is_empty() { msg.set_text(Some(arg2.to_string())); } - chat::send_msg(context, sel_chat.as_ref().unwrap().get_id(), &mut msg)?; + chat::send_msg(&context, sel_chat.as_ref().unwrap().get_id(), &mut msg).await?; } "listmsgs" => { ensure!(!arg1.is_empty(), "Argument missing."); @@ -795,9 +806,9 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { ChatId::new(0) }; - let msglist = context.search_msgs(chat, arg1); + let msglist = context.search_msgs(chat, arg1).await; - log_msglist(context, &msglist)?; + log_msglist(&context, &msglist).await?; println!("{} messages.", msglist.len()); } "draft" => { @@ -810,10 +821,16 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { .as_ref() .unwrap() .get_id() - .set_draft(context, Some(&mut draft)); + .set_draft(&context, Some(&mut draft)) + .await; println!("Draft saved."); } else { - sel_chat.as_ref().unwrap().get_id().set_draft(context, None); + sel_chat + .as_ref() + .unwrap() + .get_id() + .set_draft(&context, None) + .await; println!("Draft deleted."); } } @@ -824,21 +841,22 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { ); let mut msg = Message::new(Viewtype::Text); msg.set_text(Some(arg1.to_string())); - chat::add_device_msg(context, None, Some(&mut msg))?; + chat::add_device_msg(&context, None, Some(&mut msg)).await?; } "updatedevicechats" => { - context.update_device_chats()?; + context.update_device_chats().await?; } "listmedia" => { ensure!(sel_chat.is_some(), "No chat selected."); let images = chat::get_chat_media( - context, + &context, sel_chat.as_ref().unwrap().get_id(), Viewtype::Image, Viewtype::Gif, Viewtype::Video, - ); + ) + .await; println!("{} images or videos: ", images.len()); for (i, data) in images.iter().enumerate() { if 0 == i { @@ -852,31 +870,33 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { "archive" | "unarchive" | "pin" | "unpin" => { ensure!(!arg1.is_empty(), "Argument missing."); let chat_id = ChatId::new(arg1.parse()?); - chat_id.set_visibility( - context, - match arg0 { - "archive" => ChatVisibility::Archived, - "unarchive" | "unpin" => ChatVisibility::Normal, - "pin" => ChatVisibility::Pinned, - _ => panic!("Unexpected command (This should never happen)"), - }, - )?; + chat_id + .set_visibility( + &context, + match arg0 { + "archive" => ChatVisibility::Archived, + "unarchive" | "unpin" => ChatVisibility::Normal, + "pin" => ChatVisibility::Pinned, + _ => panic!("Unexpected command (This should never happen)"), + }, + ) + .await?; } "delchat" => { ensure!(!arg1.is_empty(), "Argument missing."); let chat_id = ChatId::new(arg1.parse()?); - chat_id.delete(context)?; + chat_id.delete(&context).await?; } "msginfo" => { ensure!(!arg1.is_empty(), "Argument missing."); let id = MsgId::new(arg1.parse()?); - let res = message::get_msg_info(context, id); + let res = message::get_msg_info(&context, id).await; println!("{}", res); } "listfresh" => { - let msglist = context.get_fresh_msgs(); + let msglist = context.get_fresh_msgs().await; - log_msglist(context, &msglist)?; + log_msglist(&context, &msglist).await?; print!("{} fresh messages.", msglist.len()); } "forward" => { @@ -888,37 +908,38 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { let mut msg_ids = [MsgId::new(0); 1]; let chat_id = ChatId::new(arg2.parse()?); msg_ids[0] = MsgId::new(arg1.parse()?); - chat::forward_msgs(context, &msg_ids, chat_id)?; + chat::forward_msgs(&context, &msg_ids, chat_id).await?; } "markseen" => { ensure!(!arg1.is_empty(), "Argument missing."); - let mut msg_ids = [MsgId::new(0); 1]; + let mut msg_ids = vec![MsgId::new(0)]; msg_ids[0] = MsgId::new(arg1.parse()?); - message::markseen_msgs(context, &msg_ids); + message::markseen_msgs(&context, msg_ids).await; } "star" | "unstar" => { ensure!(!arg1.is_empty(), "Argument missing."); - let mut msg_ids = [MsgId::new(0); 1]; + let mut msg_ids = vec![MsgId::new(0); 1]; msg_ids[0] = MsgId::new(arg1.parse()?); - message::star_msgs(context, &msg_ids, arg0 == "star"); + message::star_msgs(&context, msg_ids, arg0 == "star").await; } "delmsg" => { ensure!(!arg1.is_empty(), "Argument missing."); let mut ids = [MsgId::new(0); 1]; ids[0] = MsgId::new(arg1.parse()?); - message::delete_msgs(context, &ids); + message::delete_msgs(&context, &ids).await; } "listcontacts" | "contacts" | "listverified" => { let contacts = Contact::get_all( - context, + &context, if arg0 == "listverified" { 0x1 | 0x2 } else { 0x2 }, Some(arg1), - )?; - log_contactlist(context, &contacts); + ) + .await?; + log_contactlist(&context, &contacts).await; println!("{} contacts.", contacts.len()); } "addcontact" => { @@ -926,30 +947,30 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { if !arg2.is_empty() { let book = format!("{}\n{}", arg1, arg2); - Contact::add_address_book(context, book)?; + Contact::add_address_book(&context, book).await?; } else { - Contact::create(context, "", arg1)?; + Contact::create(&context, "", arg1).await?; } } "contactinfo" => { ensure!(!arg1.is_empty(), "Argument missing."); let contact_id = arg1.parse()?; - let contact = Contact::get_by_id(context, contact_id)?; + let contact = Contact::get_by_id(&context, contact_id).await?; let name_n_addr = contact.get_name_n_addr(); let mut res = format!( "Contact info for: {}:\nIcon: {}\n", name_n_addr, - match contact.get_profile_image(context) { + match contact.get_profile_image(&context).await { Some(image) => image.to_str().unwrap().to_string(), None => "NoIcon".to_string(), } ); - res += &Contact::get_encrinfo(context, contact_id)?; + res += &Contact::get_encrinfo(&context, contact_id).await?; - let chatlist = Chatlist::try_load(context, 0, None, Some(contact_id))?; + let chatlist = Chatlist::try_load(&context, 0, None, Some(contact_id)).await?; let chatlist_cnt = chatlist.len(); if chatlist_cnt > 0 { res += &format!( @@ -960,7 +981,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { if 0 != i { res += ", "; } - let chat = Chat::load_from_db(context, chatlist.get_chat_id(i))?; + let chat = Chat::load_from_db(&context, chatlist.get_chat_id(i)).await?; res += &format!("{}#{}", chat_prefix(&chat), chat.get_id()); } } @@ -969,11 +990,11 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { } "delcontact" => { ensure!(!arg1.is_empty(), "Argument missing."); - Contact::delete(context, arg1.parse()?)?; + Contact::delete(&context, arg1.parse()?).await?; } "checkqr" => { ensure!(!arg1.is_empty(), "Argument missing."); - let res = check_qr(context, arg1); + let res = check_qr(&context, arg1).await; println!( "state={}, id={}, text1={:?}, text2={:?}", res.get_state(), @@ -984,7 +1005,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { } "setqr" => { ensure!(!arg1.is_empty(), "Argument missing."); - match set_config_from_qr(context, arg1) { + match set_config_from_qr(&context, arg1).await { Ok(()) => println!("Config set from QR code, you can now call 'configure'"), Err(err) => println!("Cannot set config from QR code: {:?}", err), } @@ -1021,7 +1042,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { "fileinfo" => { ensure!(!arg1.is_empty(), "Argument missing."); - if let Ok(buf) = dc_read_file(context, &arg1) { + if let Ok(buf) = dc_read_file(&context, &arg1).await { let (width, height) = dc_get_filemeta(&buf)?; println!("width={}, height={}", width, height); } else { @@ -1031,7 +1052,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { "emptyserver" => { ensure!(!arg1.is_empty(), "Argument missing"); - message::dc_empty_server(context, arg1.parse()?); + message::dc_empty_server(&context, arg1.parse()?).await; } "" => (), _ => bail!("Unknown command: \"{}\" type ? for help.", arg0), diff --git a/examples/repl/main.rs b/examples/repl/main.rs index c1873829c..f30e848b8 100644 --- a/examples/repl/main.rs +++ b/examples/repl/main.rs @@ -8,25 +8,20 @@ extern crate deltachat; #[macro_use] extern crate failure; -#[macro_use] -extern crate lazy_static; -#[macro_use] -extern crate rusqlite; use std::borrow::Cow::{self, Borrowed, Owned}; use std::io::{self, Write}; -use std::path::Path; use std::process::Command; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Arc, Mutex, RwLock}; +use ansi_term::Color; +use async_std::path::Path; use deltachat::chat::ChatId; use deltachat::config; use deltachat::context::*; -use deltachat::job::*; use deltachat::oauth2::*; use deltachat::securejoin::*; use deltachat::Event; +use log::{error, info, warn}; use rustyline::completion::{Completer, FilenameCompleter, Pair}; use rustyline::config::OutputStreamType; use rustyline::error::ReadlineError; @@ -39,179 +34,83 @@ use rustyline::{ mod cmdline; use self::cmdline::*; -// Event Handler - -fn receive_event(_context: &Context, event: Event) { +/// Event Handler +fn receive_event(event: Event) { + let yellow = Color::Yellow.normal(); match event { Event::Info(msg) => { /* do not show the event as this would fill the screen */ - println!("{}", msg); + info!("{}", msg); } Event::SmtpConnected(msg) => { - println!("[DC_EVENT_SMTP_CONNECTED] {}", msg); + info!("[SMTP_CONNECTED] {}", msg); } Event::ImapConnected(msg) => { - println!("[DC_EVENT_IMAP_CONNECTED] {}", msg); + info!("[IMAP_CONNECTED] {}", msg); } Event::SmtpMessageSent(msg) => { - println!("[DC_EVENT_SMTP_MESSAGE_SENT] {}", msg); + info!("[SMTP_MESSAGE_SENT] {}", msg); } Event::Warning(msg) => { - println!("[Warning] {}", msg); + warn!("{}", msg); } Event::Error(msg) => { - println!("\x1b[31m[DC_EVENT_ERROR] {}\x1b[0m", msg); + error!("{}", msg); } Event::ErrorNetwork(msg) => { - println!("\x1b[31m[DC_EVENT_ERROR_NETWORK] msg={}\x1b[0m", msg); + error!("[NETWORK] msg={}", msg); } Event::ErrorSelfNotInGroup(msg) => { - println!("\x1b[31m[DC_EVENT_ERROR_SELF_NOT_IN_GROUP] {}\x1b[0m", msg); + error!("[SELF_NOT_IN_GROUP] {}", msg); } Event::MsgsChanged { chat_id, msg_id } => { - print!( - "\x1b[33m{{Received DC_EVENT_MSGS_CHANGED(chat_id={}, msg_id={})}}\n\x1b[0m", - chat_id, msg_id, + info!( + "{}", + yellow.paint(format!( + "Received MSGS_CHANGED(chat_id={}, msg_id={})", + chat_id, msg_id, + )) ); } Event::ContactsChanged(_) => { - print!("\x1b[33m{{Received DC_EVENT_CONTACTS_CHANGED()}}\n\x1b[0m"); + info!("{}", yellow.paint("Received CONTACTS_CHANGED()")); } Event::LocationChanged(contact) => { - print!( - "\x1b[33m{{Received DC_EVENT_LOCATION_CHANGED(contact={:?})}}\n\x1b[0m", - contact, + info!( + "{}", + yellow.paint(format!("Received LOCATION_CHANGED(contact={:?})", contact)) ); } Event::ConfigureProgress(progress) => { - print!( - "\x1b[33m{{Received DC_EVENT_CONFIGURE_PROGRESS({} ‰)}}\n\x1b[0m", - progress, + info!( + "{}", + yellow.paint(format!("Received CONFIGURE_PROGRESS({} ‰)", progress)) ); } Event::ImexProgress(progress) => { - print!( - "\x1b[33m{{Received DC_EVENT_IMEX_PROGRESS({} ‰)}}\n\x1b[0m", - progress, + info!( + "{}", + yellow.paint(format!("Received IMEX_PROGRESS({} ‰)", progress)) ); } Event::ImexFileWritten(file) => { - print!( - "\x1b[33m{{Received DC_EVENT_IMEX_FILE_WRITTEN({})}}\n\x1b[0m", - file.display() + info!( + "{}", + yellow.paint(format!("Received IMEX_FILE_WRITTEN({})", file.display())) ); } Event::ChatModified(chat) => { - print!( - "\x1b[33m{{Received DC_EVENT_CHAT_MODIFIED({})}}\n\x1b[0m", - chat + info!( + "{}", + yellow.paint(format!("Received CHAT_MODIFIED({})", chat)) ); } _ => { - print!("\x1b[33m{{Received {:?}}}\n\x1b[0m", event); + info!("Received {:?}", event); } } } -// Threads for waiting for messages and for jobs - -lazy_static! { - static ref HANDLE: Arc>> = Arc::new(Mutex::new(None)); - static ref IS_RUNNING: AtomicBool = AtomicBool::new(true); -} - -struct Handle { - handle_imap: Option>, - handle_mvbox: Option>, - handle_sentbox: Option>, - handle_smtp: Option>, -} - -macro_rules! while_running { - ($code:block) => { - if IS_RUNNING.load(Ordering::Relaxed) { - $code - } else { - break; - } - }; -} - -fn start_threads(c: Arc>) { - if HANDLE.clone().lock().unwrap().is_some() { - return; - } - - println!("Starting threads"); - IS_RUNNING.store(true, Ordering::Relaxed); - - let ctx = c.clone(); - let handle_imap = std::thread::spawn(move || loop { - while_running!({ - perform_inbox_jobs(&ctx.read().unwrap()); - perform_inbox_fetch(&ctx.read().unwrap()); - while_running!({ - let context = ctx.read().unwrap(); - perform_inbox_idle(&context); - }); - }); - }); - - let ctx = c.clone(); - let handle_mvbox = std::thread::spawn(move || loop { - while_running!({ - perform_mvbox_fetch(&ctx.read().unwrap()); - while_running!({ - perform_mvbox_idle(&ctx.read().unwrap()); - }); - }); - }); - - let ctx = c.clone(); - let handle_sentbox = std::thread::spawn(move || loop { - while_running!({ - perform_sentbox_fetch(&ctx.read().unwrap()); - while_running!({ - perform_sentbox_idle(&ctx.read().unwrap()); - }); - }); - }); - - let ctx = c; - let handle_smtp = std::thread::spawn(move || loop { - while_running!({ - perform_smtp_jobs(&ctx.read().unwrap()); - while_running!({ - perform_smtp_idle(&ctx.read().unwrap()); - }); - }); - }); - - *HANDLE.clone().lock().unwrap() = Some(Handle { - handle_imap: Some(handle_imap), - handle_mvbox: Some(handle_mvbox), - handle_sentbox: Some(handle_sentbox), - handle_smtp: Some(handle_smtp), - }); -} - -fn stop_threads(context: &Context) { - if let Some(ref mut handle) = *HANDLE.clone().lock().unwrap() { - println!("Stopping threads"); - IS_RUNNING.store(false, Ordering::Relaxed); - - interrupt_inbox_idle(context); - interrupt_mvbox_idle(context); - interrupt_sentbox_idle(context); - interrupt_smtp_idle(context); - - handle.handle_imap.take().unwrap().join().unwrap(); - handle.handle_mvbox.take().unwrap().join().unwrap(); - handle.handle_sentbox.take().unwrap().join().unwrap(); - handle.handle_smtp.take().unwrap().join().unwrap(); - } -} - // === The main loop struct DcHelper { @@ -361,21 +260,22 @@ impl Highlighter for DcHelper { impl Helper for DcHelper {} -fn main_0(args: Vec) -> Result<(), failure::Error> { +async fn start(args: Vec) -> Result<(), failure::Error> { if args.len() < 2 { println!("Error: Bad arguments, expected [db-name]."); return Err(format_err!("No db-name specified")); } - let context = Context::new( - Box::new(receive_event), - "CLI".into(), - Path::new(&args[1]).to_path_buf(), - )?; + let context = Context::new("CLI".into(), Path::new(&args[1]).to_path_buf()).await?; + + let ctx = context.clone(); + std::thread::spawn(move || loop { + if let Ok(event) = ctx.get_next_event() { + receive_event(event); + } + }); println!("Delta Chat Core is awaiting your commands."); - let ctx = Arc::new(RwLock::new(context)); - let config = Config::builder() .history_ignore_space(true) .completion_type(CompletionType::List) @@ -395,6 +295,8 @@ fn main_0(args: Vec) -> Result<(), failure::Error> { println!("No previous history."); } + let mut selected_chat = ChatId::default(); + loop { let p = "> "; let readline = rl.readline(&p); @@ -402,8 +304,7 @@ fn main_0(args: Vec) -> Result<(), failure::Error> { Ok(line) => { // TODO: ignore "set mail_pw" rl.add_history_entry(line.as_str()); - let ctx = ctx.clone(); - match handle_cmd(line.trim(), ctx) { + match handle_cmd(line.trim(), context.clone(), &mut selected_chat).await { Ok(ExitResult::Continue) => {} Ok(ExitResult::Exit) => break, Err(err) => println!("Error: {}", err), @@ -421,9 +322,8 @@ fn main_0(args: Vec) -> Result<(), failure::Error> { } rl.save_history(".dc-history.txt")?; println!("history saved"); - { - stop_threads(&ctx.read().unwrap()); - } + + context.stop().await; Ok(()) } @@ -434,43 +334,29 @@ enum ExitResult { Exit, } -fn handle_cmd(line: &str, ctx: Arc>) -> Result { +async fn handle_cmd( + line: &str, + ctx: Context, + selected_chat: &mut ChatId, +) -> Result { let mut args = line.splitn(2, ' '); let arg0 = args.next().unwrap_or_default(); let arg1 = args.next().unwrap_or_default(); match arg0 { "connect" => { - start_threads(ctx); + ctx.run().await; } "disconnect" => { - stop_threads(&ctx.read().unwrap()); - } - "smtp-jobs" => { - if HANDLE.clone().lock().unwrap().is_some() { - println!("smtp-jobs are already running in a thread.",); - } else { - perform_smtp_jobs(&ctx.read().unwrap()); - } - } - "imap-jobs" => { - if HANDLE.clone().lock().unwrap().is_some() { - println!("inbox-jobs are already running in a thread."); - } else { - perform_inbox_jobs(&ctx.read().unwrap()); - } + ctx.stop().await; } "configure" => { - start_threads(ctx.clone()); - ctx.read().unwrap().configure(); + ctx.configure().await?; } "oauth2" => { - if let Some(addr) = ctx.read().unwrap().get_config(config::Config::Addr) { - let oauth2_url = dc_get_oauth2_url( - &ctx.read().unwrap(), - &addr, - "chat.delta:/com.b44t.messenger", - ); + if let Some(addr) = ctx.get_config(config::Config::Addr).await { + let oauth2_url = + dc_get_oauth2_url(&ctx, &addr, "chat.delta:/com.b44t.messenger").await; if oauth2_url.is_none() { println!("OAuth2 not available for {}.", &addr); } else { @@ -485,11 +371,10 @@ fn handle_cmd(line: &str, ctx: Arc>) -> Result { - start_threads(ctx.clone()); - if let Some(mut qr) = dc_get_securejoin_qr( - &ctx.read().unwrap(), - ChatId::new(arg1.parse().unwrap_or_default()), - ) { + ctx.run().await; + if let Some(mut qr) = + dc_get_securejoin_qr(&ctx, ChatId::new(arg1.parse().unwrap_or_default())).await + { if !qr.is_empty() { if arg0 == "getbadqr" && qr.len() > 40 { qr.replace_range(12..22, "0000000000") @@ -505,23 +390,23 @@ fn handle_cmd(line: &str, ctx: Arc>) -> Result { - start_threads(ctx.clone()); + ctx.run().await; if !arg0.is_empty() { - dc_join_securejoin(&ctx.read().unwrap(), arg1); + dc_join_securejoin(&ctx, arg1).await; } } "exit" | "quit" => return Ok(ExitResult::Exit), - _ => dc_cmdline(&ctx.read().unwrap(), line)?, + _ => cmdline(ctx.clone(), line, selected_chat).await?, } Ok(ExitResult::Continue) } -pub fn main() -> Result<(), failure::Error> { +fn main() -> Result<(), failure::Error> { let _ = pretty_env_logger::try_init(); - let args: Vec = std::env::args().collect(); - main_0(args)?; + let args = std::env::args().collect(); + async_std::task::block_on(async move { start(args).await })?; Ok(()) } diff --git a/src/contact.rs b/src/contact.rs index cb3ab93be..6db5a0943 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -674,7 +674,7 @@ impl Contact { let mut ret = String::new(); if let Ok(contact) = Contact::load_from_db(context, contact_id).await { - let peerstate = Peerstate::from_addr(context, &context.sql, &contact.addr).await; + let peerstate = Peerstate::from_addr(context, &contact.addr).await; let loginparam = LoginParam::from_database(context, "configured_").await; let mut self_key = Key::from_self_public(context, &loginparam.addr, &context.sql).await; @@ -940,7 +940,7 @@ impl Contact { } } - let peerstate = Peerstate::from_addr(context, &context.sql, &self.addr).await; + let peerstate = Peerstate::from_addr(context, &self.addr).await; if let Some(ps) = peerstate { if ps.verified_key.is_some() { return VerifiedStatus::BidirectVerified; diff --git a/src/context.rs b/src/context.rs index f5545c6ab..879602d4f 100644 --- a/src/context.rs +++ b/src/context.rs @@ -136,10 +136,22 @@ impl Context { l.run(self.clone()); } + pub async fn is_running(&self) -> bool { + self.inner.scheduler.read().await.is_running() + } + pub async fn stop(&self) { self.inner.stop().await; } + /// Returns a reference to the underlying SQL instance. + /// + /// Warning: this is only here for testing, not part of the public API. + #[cfg(feature = "internals")] + pub fn sql(&self) -> &Sql { + &self.inner.sql + } + /// Returns database file path. pub fn get_dbfile(&self) -> &Path { self.dbfile.as_path() diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index 4433c08bd..36c5f6972 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -1434,7 +1434,7 @@ async fn check_verified_properties( // this check is skipped for SELF as there is no proper SELF-peerstate // and results in group-splits otherwise. if from_id != DC_CONTACT_ID_SELF { - let peerstate = Peerstate::from_addr(context, &context.sql, contact.get_addr()).await; + let peerstate = Peerstate::from_addr(context, contact.get_addr()).await; if peerstate.is_none() || contact.is_verified_ex(context, peerstate.as_ref()).await @@ -1488,7 +1488,7 @@ async fn check_verified_properties( context.is_self_addr(&to_addr).await ); let mut is_verified = _is_verified != 0; - let peerstate = Peerstate::from_addr(context, &context.sql, &to_addr).await; + let peerstate = Peerstate::from_addr(context, &to_addr).await; // mark gossiped keys (if any) as verified if mimeparser.gossipped_addr.contains(&to_addr) { diff --git a/src/e2ee.rs b/src/e2ee.rs index 549ee4b72..1c6c421ce 100644 --- a/src/e2ee.rs +++ b/src/e2ee.rs @@ -137,7 +137,7 @@ pub async fn try_decrypt( let autocryptheader = Aheader::from_headers(context, &from, &mail.headers); if message_time > 0 { - peerstate = Peerstate::from_addr(context, &context.sql, &from).await; + peerstate = Peerstate::from_addr(context, &from).await; if let Some(ref mut peerstate) = peerstate { if let Some(ref header) = autocryptheader { @@ -167,7 +167,7 @@ pub async fn try_decrypt( .await { if peerstate.as_ref().map(|p| p.last_seen).unwrap_or_else(|| 0) == 0 { - peerstate = Peerstate::from_addr(&context, &context.sql, &from).await; + peerstate = Peerstate::from_addr(&context, &from).await; } if let Some(ref peerstate) = peerstate { if peerstate.degrade_event.is_some() { diff --git a/src/lib.rs b/src/lib.rs index 0012e2fdc..2bcd59ee3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,20 +20,18 @@ pub trait ToSql: rusqlite::ToSql + Send + Sync {} impl ToSql for T {} -macro_rules! paramsv { - () => { - Vec::new() - }; - ($($param:expr),+ $(,)?) => { - vec![$(&$param as &dyn $crate::ToSql),+] - }; -} - #[macro_use] pub mod log; #[macro_use] pub mod error; +#[cfg(feature = "internals")] +#[macro_use] +pub mod sql; +#[cfg(not(feature = "internals"))] +#[macro_use] +mod sql; + pub mod headerdef; pub(crate) mod events; @@ -71,7 +69,6 @@ pub mod qr; pub mod securejoin; mod simplify; mod smtp; -mod sql; pub mod stock; mod token; #[macro_use] diff --git a/src/log.rs b/src/log.rs index dbd298bcd..db56c1f70 100644 --- a/src/log.rs +++ b/src/log.rs @@ -7,9 +7,7 @@ macro_rules! info { }; ($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {{ let formatted = format!($msg, $($args),*); - let thread = ::std::thread::current(); - let full = format!("{thid:?} {file}:{line}: {msg}", - thid = thread.id(), + let full = format!("{file}:{line}: {msg}", file = file!(), line = line!(), msg = &formatted); @@ -24,9 +22,7 @@ macro_rules! warn { }; ($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {{ let formatted = format!($msg, $($args),*); - let thread = ::std::thread::current(); - let full = format!("{thid:?} {file}:{line}: {msg}", - thid = thread.id(), + let full = format!("{file}:{line}: {msg}", file = file!(), line = line!(), msg = &formatted); diff --git a/src/mimefactory.rs b/src/mimefactory.rs index a541b5997..b4f73f95f 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -240,7 +240,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> { .filter(|(_, addr)| addr != &self_addr) { res.push(( - Peerstate::from_addr(self.context, &self.context.sql, addr).await, + Peerstate::from_addr(self.context, addr).await, addr.as_str(), )); } diff --git a/src/mimeparser.rs b/src/mimeparser.rs index 707d7afeb..7b1d8388c 100644 --- a/src/mimeparser.rs +++ b/src/mimeparser.rs @@ -886,7 +886,7 @@ async fn update_gossip_peerstates( .unwrap() .contains(&header.addr.to_lowercase()) { - let mut peerstate = Peerstate::from_addr(context, &context.sql, &header.addr).await; + let mut peerstate = Peerstate::from_addr(context, &header.addr).await; if let Some(ref mut peerstate) = peerstate { peerstate.apply_gossip(header, message_time); peerstate.save_to_db(&context.sql, false).await?; diff --git a/src/peerstate.rs b/src/peerstate.rs index a2cbc6ef1..beec1bbf5 100644 --- a/src/peerstate.rs +++ b/src/peerstate.rs @@ -144,7 +144,7 @@ impl<'a> Peerstate<'a> { res } - pub async fn from_addr(context: &'a Context, _sql: &Sql, addr: &str) -> Option> { + pub async fn from_addr(context: &'a Context, addr: &str) -> Option> { let query = "SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, verified_key, verified_key_fingerprint FROM acpeerstates WHERE addr=? COLLATE NOCASE;"; Self::from_stmt(context, query, paramsv![addr]).await } @@ -510,7 +510,7 @@ mod tests { "failed to save to db" ); - let peerstate_new = Peerstate::from_addr(&ctx.ctx, &ctx.ctx.sql, addr) + let peerstate_new = Peerstate::from_addr(&ctx.ctx, addr) .await .expect("failed to load peerstate from db"); @@ -586,7 +586,7 @@ mod tests { "failed to save" ); - let peerstate_new = Peerstate::from_addr(&ctx.ctx, &ctx.ctx.sql, addr) + let peerstate_new = Peerstate::from_addr(&ctx.ctx, addr) .await .expect("failed to load peerstate from db"); diff --git a/src/scheduler.rs b/src/scheduler.rs index 7f86d03e4..70b99220f 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -217,7 +217,6 @@ impl Scheduler { task::spawn(async move { smtp_loop(ctx1, smtp_handlers).await }); info!(ctx, "scheduler is running"); - println!("RUN DONE"); } fn set_probe_network(&mut self, val: bool) { diff --git a/src/securejoin.rs b/src/securejoin.rs index 6998c4f7b..f6c445009 100644 --- a/src/securejoin.rs +++ b/src/securejoin.rs @@ -349,9 +349,7 @@ async fn fingerprint_equals_sender( if contacts.len() == 1 { if let Ok(contact) = Contact::load_from_db(context, contacts[0]).await { - if let Some(peerstate) = - Peerstate::from_addr(context, &context.sql, contact.get_addr()).await - { + if let Some(peerstate) = Peerstate::from_addr(context, contact.get_addr()).await { let fingerprint_normalized = dc_normalize_fingerprint(fingerprint.as_ref()); if peerstate.public_key_fingerprint.is_some() && &fingerprint_normalized == peerstate.public_key_fingerprint.as_ref().unwrap() diff --git a/src/sql.rs b/src/sql.rs index 0d25d7757..0d84580e5 100644 --- a/src/sql.rs +++ b/src/sql.rs @@ -17,6 +17,16 @@ use crate::dc_tools::*; use crate::param::*; use crate::peerstate::*; +#[macro_export] +macro_rules! paramsv { + () => { + Vec::new() + }; + ($($param:expr),+ $(,)?) => { + vec![$(&$param as &dyn $crate::ToSql),+] + }; +} + #[derive(Debug, Fail)] pub enum Error { #[fail(display = "Sqlite Error: {:?}", _0)] @@ -1295,7 +1305,7 @@ async fn open( ) .await?; for addr in &addrs { - if let Some(ref mut peerstate) = Peerstate::from_addr(context, sql, addr).await { + if let Some(ref mut peerstate) = Peerstate::from_addr(context, addr).await { peerstate.recalc_fingerprint(); peerstate.save_to_db(sql, false).await?; } From 8a7eaba668da2ed8bf563f838988d90b89c7ed03 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Sat, 21 Mar 2020 16:26:27 +0100 Subject: [PATCH 022/118] fix imap fetch loop and watch folders --- examples/repl/main.rs | 8 +- src/configure/mod.rs | 2 - src/imap/mod.rs | 174 +++++++++++++++++++----------------------- src/scheduler.rs | 113 ++++++++++++++------------- 4 files changed, 146 insertions(+), 151 deletions(-) diff --git a/examples/repl/main.rs b/examples/repl/main.rs index f30e848b8..f79988ccb 100644 --- a/examples/repl/main.rs +++ b/examples/repl/main.rs @@ -269,8 +269,12 @@ async fn start(args: Vec) -> Result<(), failure::Error> { let ctx = context.clone(); std::thread::spawn(move || loop { - if let Ok(event) = ctx.get_next_event() { - receive_event(event); + if ctx.has_next_event() { + if let Ok(event) = ctx.get_next_event() { + receive_event(event); + } + } else { + std::thread::sleep(std::time::Duration::from_millis(50)); } }); diff --git a/src/configure/mod.rs b/src/configure/mod.rs index 837ce5648..92b4b32ec 100644 --- a/src/configure/mod.rs +++ b/src/configure/mod.rs @@ -423,9 +423,7 @@ async fn exec_step( // "configured_" prefix; also write the "configured"-flag */ // the trailing underscore is correct param.save_to_database(ctx, "configured_").await?; - println!("storing configured val"); ctx.sql.set_raw_config_bool(ctx, "configured", true).await?; - println!("stored configured val"); } 18 => { progress!(ctx, 920); diff --git a/src/imap/mod.rs b/src/imap/mod.rs index b1897394b..1082f95f3 100644 --- a/src/imap/mod.rs +++ b/src/imap/mod.rs @@ -345,18 +345,14 @@ impl Imap { } async fn unsetup_handle(&mut self, context: &Context) { - info!(context, "IMAP unsetup_handle step 2"); if let Some(mut session) = self.session.take() { if let Err(err) = session.close().await { warn!(context, "failed to close connection: {:?}", err); } } self.connected = false; - - info!(context, "IMAP unsetup_handle step 3 (clearing config)."); self.config.selected_folder = None; self.config.selected_mailbox = None; - info!(context, "IMAP unsetup_handle step 4 (disconnected)"); } async fn free_connect_params(&mut self) { @@ -613,91 +609,93 @@ impl Imap { .await?; let mut read_cnt: usize = 0; + let mut read_errors = 0; // prefetch info from all unfetched mails let mut new_last_seen_uid = last_seen_uid; - let mut read_errors: usize = 0; - let mut uids = Vec::new(); - if let Some(ref mut session) = &mut self.session { - // fetch messages with larger UID than the last one seen - // `(UID FETCH lastseenuid+1:*)`, see RFC 4549 - let set = format!("{}:*", last_seen_uid + 1); - let mut list = match session.uid_fetch(set, PREFETCH_FLAGS).await { - Ok(list) => list, - Err(err) => { - return Err(Error::FetchFailed(err)); - } - }; + if self.session.is_none() { + return Err(Error::NoConnection); + } + let session = self.session.as_mut().unwrap(); - while let Some(fetch) = list.next().await { - let fetch = fetch.map_err(|err| Error::Other(err.to_string()))?; - let cur_uid = fetch.uid.unwrap_or_default(); - if cur_uid <= last_seen_uid { - // If the mailbox is not empty, results always include - // at least one UID, even if last_seen_uid+1 is past - // the last UID in the mailbox. It happens because - // uid+1:* is interpreted the same way as *:uid+1. - // See https://tools.ietf.org/html/rfc3501#page-61 for - // standard reference. Therefore, sometimes we receive - // already seen messages and have to filter them out. + // fetch messages with larger UID than the last one seen + // `(UID FETCH lastseenuid+1:*)`, see RFC 4549 + let set = format!("{}:*", last_seen_uid + 1); + let mut list = match session.uid_fetch(set, PREFETCH_FLAGS).await { + Ok(list) => list, + Err(err) => { + return Err(Error::FetchFailed(err)); + } + }; + + let mut msgs = Vec::new(); + while let Some(fetch) = list.next().await { + let fetch = fetch.map_err(|err| Error::Other(err.to_string()))?; + msgs.push(fetch); + } + drop(list); + + msgs.sort_unstable_by_key(|msg| msg.uid.unwrap_or_default()); + + for fetch in msgs.into_iter() { + let cur_uid = fetch.uid.unwrap_or_default(); + if cur_uid <= last_seen_uid { + // If the mailbox is not empty, results always include + // at least one UID, even if last_seen_uid+1 is past + // the last UID in the mailbox. It happens because + // uid+1:* is interpreted the same way as *:uid+1. + // See https://tools.ietf.org/html/rfc3501#page-61 for + // standard reference. Therefore, sometimes we receive + // already seen messages and have to filter them out. + info!( + context, + "fetch_new_messages: ignoring uid {}, last seen was {}", cur_uid, last_seen_uid + ); + continue; + } + read_cnt += 1; + let headers = get_fetch_headers(&fetch)?; + let message_id = prefetch_get_message_id(&headers).unwrap_or_default(); + + if precheck_imf(context, &message_id, folder.as_ref(), cur_uid).await { + // we know the message-id already or don't want the message otherwise. + info!( + context, + "Skipping message {} from \"{}\" by precheck.", + message_id, + folder.as_ref(), + ); + } else { + let show = prefetch_should_download(context, &headers, show_emails) + .await + .map_err(|err| { + warn!(context, "prefetch_should_download error: {}", err); + err + }) + .unwrap_or(true); + + if !show { info!( context, - "fetch_new_messages: ignoring uid {}, last seen was {}", - cur_uid, - last_seen_uid - ); - continue; - } - read_cnt += 1; - let headers = get_fetch_headers(&fetch)?; - let message_id = prefetch_get_message_id(&headers).unwrap_or_default(); - - if precheck_imf(context, &message_id, folder.as_ref(), cur_uid).await { - // we know the message-id already or don't want the message otherwise. - info!( - context, - "Skipping message {} from \"{}\" by precheck.", + "Ignoring new message {} from \"{}\".", message_id, folder.as_ref(), ); } else { - let show = prefetch_should_download(context, &headers, show_emails) - .await - .map_err(|err| { - warn!(context, "prefetch_should_download error: {}", err); - err - }) - .unwrap_or(true); - - if !show { + // check passed, go fetch the rest + if let Err(err) = self.fetch_single_msg(context, &folder, cur_uid).await { info!( context, - "Ignoring new message {} from \"{}\".", + "Read error for message {} from \"{}\", trying over later: {}.", message_id, folder.as_ref(), + err ); - } else { - // check passed, go fetch the rest - uids.push((cur_uid, message_id)); + read_errors += 1; } } } - } else { - return Err(Error::NoConnection); - }; - - for (cur_uid, message_id) in uids.into_iter() { - if let Err(err) = self.fetch_single_msg(context, &folder, cur_uid).await { - info!( - context, - "Read error for message {} from \"{}\", trying over later: {}.", - message_id, - folder.as_ref(), - err - ); - read_errors += 1; - } if read_errors == 0 { new_last_seen_uid = cur_uid; @@ -1114,6 +1112,7 @@ impl Imap { .get_raw_config_int(context, "folders_configured") .await; if folders_configured.unwrap_or_default() >= 3 { + info!(context, "IMAP-folders already configured"); // the "3" here we increase if we have future updates to // to folder configuration return Ok(()); @@ -1139,17 +1138,19 @@ impl Imap { while let Some(folder) = folders.next().await { let folder = folder.map_err(|err| Error::Other(err.to_string()))?; + info!(context, "Scanning folder: {:?}", folder); - if folder.name() == "DeltaChat" || folder.name() == fallback_folder { + if mvbox_folder.is_none() + && (folder.name() == "DeltaChat" || folder.name() == fallback_folder) + { mvbox_folder = Some(folder.name().to_string()); } - let is_sentbox_folder = match get_folder_meaning(&folder) { - FolderMeaning::SentObjects => true, - _ => false, - }; - if is_sentbox_folder { - info!(context, "sentbox folder is {:?}", folder); - sentbox_folder = Some(folder); + + if sentbox_folder.is_none() { + if let FolderMeaning::SentObjects = get_folder_meaning(&folder) { + info!(context, "sentbox folder is {:?}", folder); + sentbox_folder = Some(folder); + } } if mvbox_folder.is_some() && sentbox_folder.is_some() { @@ -1227,23 +1228,6 @@ impl Imap { Ok(()) } - // async fn list_folders(&self, session: &mut Session, context: &Context) -> Option> { - // match session.list(Some(""), Some("*")).await { - // Ok(list) => { - // if list.is_empty() { - // warn!(context, "Folder list is empty.",); - // } - // Some(list) - // } - // Err(err) => { - // eprintln!("list error: {:?}", err); - // warn!(context, "Cannot get folder list.",); - - // None - // } - // } - // } - pub async fn empty_folder(&mut self, context: &Context, folder: &str) { info!(context, "emptying folder {}", folder); diff --git a/src/scheduler.rs b/src/scheduler.rs index 70b99220f..1f17ddb40 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -5,7 +5,6 @@ use async_std::task; use std::time::Duration; use crate::context::Context; -use crate::error::Error; use crate::imap::Imap; use crate::job::{self, Thread}; use crate::smtp::Smtp; @@ -68,11 +67,60 @@ async fn inbox_loop(ctx: Context, inbox_handlers: ImapConnectionHandlers) { ctx.scheduler.write().await.set_probe_network(false); } Ok(None) | Err(async_std::future::TimeoutError { .. }) => { - let watch_folder = get_watch_folder(&ctx, "configured_inbox_folder") - .await - .ok_or_else(|| Error::WatchFolderNotFound("not-set".to_string())) - .unwrap(); + match get_watch_folder(&ctx, "configured_inbox_folder").await { + Some(watch_folder) => { + // fetch + connection + .fetch(&ctx, &watch_folder) + .await + .unwrap_or_else(|err| { + error!(ctx, "{}", err); + }); + // idle + if connection.can_idle() { + connection + .idle(&ctx, Some(watch_folder)) + .await + .unwrap_or_else(|err| { + error!(ctx, "{}", err); + }); + } else { + connection.fake_idle(&ctx, Some(watch_folder)).await; + } + } + None => { + warn!(ctx, "Can not watch inbox folder, not set"); + connection.fake_idle(&ctx, None).await; + } + } + } + } + } + }; + + fut.race(stop_receiver.recv()).await; + shutdown_sender.send(()).await; +} + +async fn simple_imap_loop( + ctx: Context, + inbox_handlers: ImapConnectionHandlers, + folder: impl AsRef, +) { + info!(ctx, "starting simple loop for {}", folder.as_ref()); + let ImapConnectionHandlers { + mut connection, + stop_receiver, + shutdown_sender, + } = inbox_handlers; + + let fut = async move { + connection.connect_configured(&ctx).await.unwrap(); + + loop { + match get_watch_folder(&ctx, folder.as_ref()).await { + Some(watch_folder) => { // fetch connection .fetch(&ctx, &watch_folder) @@ -93,53 +141,14 @@ async fn inbox_loop(ctx: Context, inbox_handlers: ImapConnectionHandlers) { connection.fake_idle(&ctx, Some(watch_folder)).await; } } - } - } - }; - - fut.race(stop_receiver.recv()).await; - shutdown_sender.send(()).await; -} - -async fn simple_imap_loop( - ctx: Context, - inbox_handlers: ImapConnectionHandlers, - folder: impl AsRef, -) { - info!(ctx, "starting simple loop"); - let ImapConnectionHandlers { - mut connection, - stop_receiver, - shutdown_sender, - } = inbox_handlers; - - let fut = async move { - connection.connect_configured(&ctx).await.unwrap(); - - loop { - let watch_folder = get_watch_folder(&ctx, folder.as_ref()) - .await - .ok_or_else(|| Error::WatchFolderNotFound("not-set".to_string())) - .unwrap(); - - // fetch - connection - .fetch(&ctx, &watch_folder) - .await - .unwrap_or_else(|err| { - error!(ctx, "{}", err); - }); - - // idle - if connection.can_idle() { - connection - .idle(&ctx, Some(watch_folder)) - .await - .unwrap_or_else(|err| { - error!(ctx, "{}", err); - }); - } else { - connection.fake_idle(&ctx, Some(watch_folder)).await; + None => { + warn!( + &ctx, + "No watch folder found for {}, skipping", + folder.as_ref() + ); + connection.fake_idle(&ctx, None).await + } } } }; From 4d6a9e4f148ce64cb8ea1139e92c41c20b4f5567 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Sat, 21 Mar 2020 17:00:07 +0100 Subject: [PATCH 023/118] improve simple example --- examples/simple.rs | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/examples/simple.rs b/examples/simple.rs index 113064419..4474df71b 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -40,16 +40,21 @@ async fn main() { let ctx1 = ctx.clone(); std::thread::spawn(move || loop { - if let Ok(event) = ctx1.get_next_event() { - cb(event); + if ctx1.has_next_event() { + if let Ok(event) = ctx1.get_next_event() { + cb(event); + } + } else { + std::thread::sleep(time::Duration::from_millis(50)); } }); println!("configuring"); let args = std::env::args().collect::>(); - assert_eq!(args.len(), 2, "missing password"); - let pw = args[1].clone(); - ctx.set_config(config::Config::Addr, Some("d@testrun.org")) + assert_eq!(args.len(), 3, "requires email password"); + let email = args[1].clone(); + let pw = args[2].clone(); + ctx.set_config(config::Config::Addr, Some(&email)) .await .unwrap(); ctx.set_config(config::Config::MailPw, Some(&pw)) @@ -66,9 +71,12 @@ async fn main() { .await .unwrap(); let chat_id = chat::create_by_contact_id(&ctx, contact_id).await.unwrap(); - chat::send_text_msg(&ctx, chat_id, "Hi, here is my first message!".into()) - .await - .unwrap(); + + for i in 0..2 { + chat::send_text_msg(&ctx, chat_id, format!("Hi, here is my {}nth message!", i)) + .await + .unwrap(); + } println!("fetching chats.."); let chats = Chatlist::try_load(&ctx, 0, None, None).await.unwrap(); From d798356c19c19c8fb0f2e5688b6a9e4452dc0301 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Sat, 21 Mar 2020 19:05:50 +0100 Subject: [PATCH 024/118] refactor stop logic --- examples/repl/main.rs | 18 ++++++++++-------- examples/simple.rs | 14 ++++++++------ src/context.rs | 30 ++++++++++++++++-------------- src/scheduler.rs | 19 +++++++++++++++++-- 4 files changed, 51 insertions(+), 30 deletions(-) diff --git a/examples/repl/main.rs b/examples/repl/main.rs index f79988ccb..3b4f47ab5 100644 --- a/examples/repl/main.rs +++ b/examples/repl/main.rs @@ -268,13 +268,15 @@ async fn start(args: Vec) -> Result<(), failure::Error> { let context = Context::new("CLI".into(), Path::new(&args[1]).to_path_buf()).await?; let ctx = context.clone(); - std::thread::spawn(move || loop { - if ctx.has_next_event() { - if let Ok(event) = ctx.get_next_event() { - receive_event(event); + async_std::task::spawn(async move { + loop { + if ctx.has_next_event() { + if let Ok(event) = ctx.get_next_event() { + receive_event(event); + } + } else { + async_std::task::sleep(std::time::Duration::from_millis(50)).await; } - } else { - std::thread::sleep(std::time::Duration::from_millis(50)); } }); @@ -316,6 +318,7 @@ async fn start(args: Vec) -> Result<(), failure::Error> { } Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => { println!("Exiting..."); + context.stop().await; break; } Err(err) => { @@ -324,11 +327,10 @@ async fn start(args: Vec) -> Result<(), failure::Error> { } } } + rl.save_history(".dc-history.txt")?; println!("history saved"); - context.stop().await; - Ok(()) } diff --git a/examples/simple.rs b/examples/simple.rs index 4474df71b..a94eab63e 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -39,13 +39,15 @@ async fn main() { println!("info: {:#?}", info); let ctx1 = ctx.clone(); - std::thread::spawn(move || loop { - if ctx1.has_next_event() { - if let Ok(event) = ctx1.get_next_event() { - cb(event); + async_std::task::spawn(async move { + loop { + if ctx1.has_next_event() { + if let Ok(event) = ctx1.get_next_event() { + cb(event); + } + } else { + async_std::task::sleep(time::Duration::from_millis(50)); } - } else { - std::thread::sleep(time::Duration::from_millis(50)); } }); diff --git a/src/context.rs b/src/context.rs index 879602d4f..85ea121f6 100644 --- a/src/context.rs +++ b/src/context.rs @@ -128,8 +128,8 @@ impl Context { } pub async fn run(&self) { - if self.inner.scheduler.read().await.is_running() { - panic!("Already running"); + if self.is_running().await { + return; } let l = &mut *self.inner.scheduler.write().await; @@ -137,7 +137,7 @@ impl Context { } pub async fn is_running(&self) -> bool { - self.inner.scheduler.read().await.is_running() + self.inner.is_running().await } pub async fn stop(&self) { @@ -480,19 +480,21 @@ impl Context { } impl InnerContext { - async fn stop(&self) { - if self.scheduler.read().await.is_running() { - self.scheduler.write().await.stop().await; - } + async fn is_running(&self) -> bool { + self.scheduler.read().await.is_running() } -} -impl Drop for InnerContext { - fn drop(&mut self) { - async_std::task::block_on(async move { - self.stop().await; - self.sql.close().await; - }); + async fn stop(&self) { + if self.is_running().await { + let token = { + let lock = &*self.scheduler.read().await; + lock.pre_stop().await + }; + { + let lock = &mut *self.scheduler.write().await; + lock.stop(token).await; + } + } } } diff --git a/src/scheduler.rs b/src/scheduler.rs index 1f17ddb40..3af84f15b 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -9,6 +9,8 @@ use crate::imap::Imap; use crate::job::{self, Thread}; use crate::smtp::Smtp; +pub(crate) struct StopToken; + /// Job and connection scheduler. #[derive(Debug)] pub(crate) enum Scheduler { @@ -287,8 +289,8 @@ impl Scheduler { } } - /// Halt the scheduler, panics if it is already stopped. - pub async fn stop(&mut self) { + /// Halts the scheduler, must be called first, and then `stop`. + pub(crate) async fn pre_stop(&self) -> StopToken { match self { Scheduler::Stopped => { panic!("WARN: already stopped"); @@ -306,6 +308,19 @@ impl Scheduler { .join(sentbox.stop()) .join(smtp.stop()) .await; + + StopToken + } + } + } + + /// Halt the scheduler, must only be called after pre_stop. + pub(crate) async fn stop(&mut self, _t: StopToken) { + match self { + Scheduler::Stopped => { + panic!("WARN: already stopped"); + } + Scheduler::Running { .. } => { *self = Scheduler::Stopped; } } From 121460954612cf53bb4b252ad2e8673f4463be47 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Sat, 21 Mar 2020 23:13:38 +0100 Subject: [PATCH 025/118] get ffi api to compile --- Cargo.lock | 1 + deltachat-ffi/Cargo.toml | 1 + deltachat-ffi/deltachat.h | 307 +------ deltachat-ffi/src/lib.rs | 1653 ++++++++++++++++++------------------- 4 files changed, 818 insertions(+), 1144 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e7f163ace..bbb9bcd6b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -697,6 +697,7 @@ dependencies = [ name = "deltachat_ffi" version = "1.27.0" dependencies = [ + "async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "deltachat 1.27.0", "failure 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "human-panic 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/deltachat-ffi/Cargo.toml b/deltachat-ffi/Cargo.toml index 2f37da72d..4d3a77295 100644 --- a/deltachat-ffi/Cargo.toml +++ b/deltachat-ffi/Cargo.toml @@ -21,6 +21,7 @@ human-panic = "1.0.1" num-traits = "0.2.6" failure = "0.1.6" serde_json = "1.0" +async-std = "1.5.0" [features] default = ["vendored", "nightly"] diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index afa5770c2..71350aec8 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -243,7 +243,7 @@ typedef uintptr_t (*dc_callback_t) (dc_context_t* context, int event, uintptr_t * The object must be passed to the other context functions * and must be freed using dc_context_unref() after usage. */ -dc_context_t* dc_context_new (dc_callback_t cb, void* userdata, const char* os_name); +dc_context_t* dc_context_new (void* userdata, const char* os_name); /** @@ -557,311 +557,18 @@ void dc_configure (dc_context_t* context); * @return 1=context is configured and can be used; * 0=context is not configured and a configuration by dc_configure() is required. */ -int dc_is_configured (const dc_context_t* context); +int dc_is_configured (const dc_context_t* context); /** - * Execute pending imap-jobs. - * This function and dc_perform_imap_fetch() and dc_perform_imap_idle() - * must be called from the same thread, typically in a loop. - * - * Example: - * - * void* imap_thread_func(void* context) - * { - * while (true) { - * dc_perform_imap_jobs(context); - * dc_perform_imap_fetch(context); - * dc_perform_imap_idle(context); - * } - * } - * - * // start imap-thread that runs forever - * pthread_t imap_thread; - * pthread_create(&imap_thread, NULL, imap_thread_func, context); - * - * ... program runs ... - * - * // network becomes available again - - * // the interrupt causes dc_perform_imap_idle() in the thread above - * // to return so that jobs are executed and messages are fetched. - * dc_maybe_network(context); - * - * @memberof dc_context_t - * @param context The context as created by dc_context_new(). - * @return None. + * TODO: Document */ -void dc_perform_imap_jobs (dc_context_t* context); - +void dc_context_run (dc_context_t* context); /** - * Fetch new messages, if any. - * This function and dc_perform_imap_jobs() and dc_perform_imap_idle() must be called from the same thread, - * typically in a loop. - * - * See dc_perform_imap_jobs() for an example. - * - * @memberof dc_context_t - * @param context The context as created by dc_context_new(). - * @return None. + * TODO: Document */ -void dc_perform_imap_fetch (dc_context_t* context); - - -/** - * Wait for messages or jobs. - * This function and dc_perform_imap_jobs() and dc_perform_imap_fetch() must be called from the same thread, - * typically in a loop. - * - * You should call this function directly after calling dc_perform_imap_fetch(). - * - * See dc_perform_imap_jobs() for an example. - * - * @memberof dc_context_t - * @param context The context as created by dc_context_new(). - * @return None. - */ -void dc_perform_imap_idle (dc_context_t* context); - - -/** - * Interrupt waiting for imap-jobs. - * If dc_perform_imap_jobs(), dc_perform_imap_fetch() and dc_perform_imap_idle() are called in a loop, - * calling this function causes imap-jobs to be executed and messages to be fetched. - * - * dc_interrupt_imap_idle() does _not_ interrupt dc_perform_imap_jobs() or dc_perform_imap_fetch(). - * If the imap-thread is inside one of these functions when dc_interrupt_imap_idle() is called, however, - * the next call of the imap-thread to dc_perform_imap_idle() is interrupted immediately. - * - * Internally, this function is called whenever a imap-jobs should be processed - * (delete message, markseen etc.). - * - * When you need to call this function just because to get jobs done after network changes, - * use dc_maybe_network() instead. - * - * @memberof dc_context_t - * @param context The context as created by dc_context_new(). - * @return None. - */ -void dc_interrupt_imap_idle (dc_context_t* context); - - -/** - * Execute pending mvbox-jobs. - * This function and dc_perform_mvbox_fetch() and dc_perform_mvbox_idle() - * must be called from the same thread, typically in a loop. - * - * Example: - * - * void* mvbox_thread_func(void* context) - * { - * while (true) { - * dc_perform_mvbox_jobs(context); - * dc_perform_mvbox_fetch(context); - * dc_perform_mvbox_idle(context); - * } - * } - * - * // start mvbox-thread that runs forever - * pthread_t mvbox_thread; - * pthread_create(&mvbox_thread, NULL, mvbox_thread_func, context); - * - * ... program runs ... - * - * // network becomes available again - - * // the interrupt causes dc_perform_mvbox_idle() in the thread above - * // to return so that jobs are executed and messages are fetched. - * dc_maybe_network(context); - * - * @memberof dc_context_t - * @param context The context as created by dc_context_new(). - * @return None. - */ -void dc_perform_mvbox_jobs (dc_context_t* context); - - -/** - * Fetch new messages from the MVBOX, if any. - * The MVBOX is a folder on the account where chat messages are moved to. - * The moving is done to not disturb shared accounts that are used by both, - * Delta Chat and a classical MUA. - * - * @memberof dc_context_t - * @param context The context as created by dc_context_new(). - * @return None. - */ -void dc_perform_mvbox_fetch (dc_context_t* context); - - -/** - * Wait for messages or jobs in the MVBOX-thread. - * This function and dc_perform_mvbox_fetch(). - * must be called from the same thread, typically in a loop. - * - * You should call this function directly after calling dc_perform_mvbox_fetch(). - * - * See dc_perform_mvbox_fetch() for an example. - * - * @memberof dc_context_t - * @param context The context as created by dc_context_new(). - * @return None. - */ -void dc_perform_mvbox_idle (dc_context_t* context); - - -/** - * Interrupt waiting for MVBOX-fetch. - * dc_interrupt_mvbox_idle() does _not_ interrupt dc_perform_mvbox_fetch(). - * If the MVBOX-thread is inside this function when dc_interrupt_mvbox_idle() is called, however, - * the next call of the MVBOX-thread to dc_perform_mvbox_idle() is interrupted immediately. - * - * Internally, this function is called whenever a imap-jobs should be processed. - * - * When you need to call this function just because to get jobs done after network changes, - * use dc_maybe_network() instead. - * - * @memberof dc_context_t - * @param context The context as created by dc_context_new(). - * @return None. - */ -void dc_interrupt_mvbox_idle (dc_context_t* context); - - -/** - * Execute pending sentbox-jobs. - * This function and dc_perform_sentbox_fetch() and dc_perform_sentbox_idle() - * must be called from the same thread, typically in a loop. - * - * Example: - * - * void* sentbox_thread_func(void* context) - * { - * while (true) { - * dc_perform_sentbox_jobs(context); - * dc_perform_sentbox_fetch(context); - * dc_perform_sentbox_idle(context); - * } - * } - * - * // start sentbox-thread that runs forever - * pthread_t sentbox_thread; - * pthread_create(&sentbox_thread, NULL, sentbox_thread_func, context); - * - * ... program runs ... - * - * // network becomes available again - - * // the interrupt causes dc_perform_sentbox_idle() in the thread above - * // to return so that jobs are executed and messages are fetched. - * dc_maybe_network(context); - * - * @memberof dc_context_t - * @param context The context as created by dc_context_new(). - * @return None. - */ -void dc_perform_sentbox_jobs (dc_context_t* context); - - -/** - * Fetch new messages from the Sent folder, if any. - * This function and dc_perform_sentbox_idle() - * must be called from the same thread, typically in a loop. - * - * @memberof dc_context_t - * @param context The context as created by dc_context_new(). - * @return None. - */ -void dc_perform_sentbox_fetch (dc_context_t* context); - - -/** - * Wait for messages or jobs in the SENTBOX-thread. - * This function and dc_perform_sentbox_fetch() - * must be called from the same thread, typically in a loop. - * - * @memberof dc_context_t - * @param context The context as created by dc_context_new(). - * @return None. - */ -void dc_perform_sentbox_idle (dc_context_t* context); - - -/** - * Interrupt waiting for messages or jobs in the SENTBOX-thread. - * - * @memberof dc_context_t - * @param context The context as created by dc_context_new(). - * @return None. - */ -void dc_interrupt_sentbox_idle (dc_context_t* context); - - -/** - * Execute pending smtp-jobs. - * This function and dc_perform_smtp_idle() must be called from the same thread, - * typically in a loop. - * - * Example: - * - * void* smtp_thread_func(void* context) - * { - * while (true) { - * dc_perform_smtp_jobs(context); - * dc_perform_smtp_idle(context); - * } - * } - * - * // start smtp-thread that runs forever - * pthread_t smtp_thread; - * pthread_create(&smtp_thread, NULL, smtp_thread_func, context); - * - * ... program runs ... - * - * // network becomes available again - - * // the interrupt causes dc_perform_smtp_idle() in the thread above - * // to return so that jobs are executed - * dc_maybe_network(context); - * - * @memberof dc_context_t - * @param context The context as created by dc_context_new(). - * @return None. - */ -void dc_perform_smtp_jobs (dc_context_t* context); - - -/** - * Wait for smtp-jobs. - * This function and dc_perform_smtp_jobs() must be called from the same thread, - * typically in a loop. - * - * See dc_interrupt_smtp_idle() for an example. - * - * @memberof dc_context_t - * @param context The context as created by dc_context_new(). - * @return None. - */ -void dc_perform_smtp_idle (dc_context_t* context); - - -/** - * Interrupt waiting for smtp-jobs. - * If dc_perform_smtp_jobs() and dc_perform_smtp_idle() are called in a loop, - * calling this function causes jobs to be executed. - * - * dc_interrupt_smtp_idle() does _not_ interrupt dc_perform_smtp_jobs(). - * If the smtp-thread is inside this function when dc_interrupt_smtp_idle() is called, however, - * the next call of the smtp-thread to dc_perform_smtp_idle() is interrupted immediately. - * - * Internally, this function is called whenever a message is to be sent. - * - * When you need to call this function just because to get jobs done after network changes, - * use dc_maybe_network() instead. - * - * @memberof dc_context_t - * @param context The context as created by dc_context_new(). - * @return None. - */ -void dc_interrupt_smtp_idle (dc_context_t* context); - +void dc_context_shutdown(dc_context_t* context); /** * This function can be called whenever there is a hint @@ -875,6 +582,7 @@ void dc_interrupt_smtp_idle (dc_context_t* context); void dc_maybe_network (dc_context_t* context); + /** * Save a keypair as the default keys for the user. * @@ -2709,7 +2417,6 @@ dc_context_t* dc_chatlist_get_context (dc_chatlist_t* chatlist); * last-message-date: * avatar-path: path-to-blobfile * is_verified: yes/no - * @return a utf8-encoded json string containing all requested info. Must be freed using dc_str_unref(). NULL is never returned. */ char* dc_chat_get_info_json (dc_context_t* context, size_t chat_id); diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index d6cbd09b1..5d95fcaa9 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -19,9 +19,10 @@ use std::ffi::CString; use std::fmt::Write; use std::ptr; use std::str::FromStr; -use std::sync::RwLock; +use std::sync::{Arc, RwLock}; use std::time::{Duration, SystemTime}; +use async_std::task::block_on; use libc::uintptr_t; use num_traits::{FromPrimitive, ToPrimitive}; @@ -59,10 +60,9 @@ use self::string::*; /// and protected by an [RwLock]. Other than that it needs to store /// the data which is passed into [dc_context_new]. pub struct ContextWrapper { - cb: Option, userdata: *mut libc::c_void, os_name: String, - inner: RwLock>, + inner: Arc>>, } unsafe impl Send for ContextWrapper {} @@ -90,7 +90,10 @@ impl ContextWrapper { /// Like [error] but logs as a warning which only goes to the /// logfile rather than being shown directly to the user. unsafe fn warning(&self, msg: &str) { - self.translate_cb(Event::Warning(msg.to_string())); + self.with_inner(|ctx| { + ctx.call_cb(Event::Warning(msg.to_string())); + }) + .unwrap(); } /// Unlock the context and execute a closure with it. @@ -108,113 +111,63 @@ impl ContextWrapper { /// the appropriate return value for an error return since this /// differs for various functions on the FFI API: sometimes 0, /// NULL, an empty string etc. - unsafe fn with_inner(&self, ctxfn: F) -> Result + fn with_inner(&self, ctxfn: F) -> Result where F: FnOnce(&Context) -> T, - { - self.try_inner(|ctx| Ok(ctxfn(ctx))).map_err(|err| { - self.warning(&err.to_string()); - }) - } - - /// Unlock the context and execute a closure with it. - /// - /// This is like [ContextWrapper::with_inner] but uses - /// [failure::Error] as error type. This allows you to write a - /// closure which could produce many errors, use the `?` operator - /// to return them and handle them all as the return of this call. - fn try_inner(&self, ctxfn: F) -> Result - where - F: FnOnce(&Context) -> Result, { let guard = self.inner.read().unwrap(); match guard.as_ref() { - Some(ref ctx) => ctxfn(ctx), - None => Err(failure::err_msg("context not open")), - } - } - - /// Translates the callback from the rust style to the C-style version. - unsafe fn translate_cb(&self, event: Event) { - if let Some(ffi_cb) = self.cb { - let event_id = event.as_id(); - match event { - Event::Info(msg) - | Event::SmtpConnected(msg) - | Event::ImapConnected(msg) - | Event::SmtpMessageSent(msg) - | Event::ImapMessageDeleted(msg) - | Event::ImapMessageMoved(msg) - | Event::ImapFolderEmptied(msg) - | Event::NewBlobFile(msg) - | Event::DeletedBlobFile(msg) - | Event::Warning(msg) - | Event::Error(msg) - | Event::ErrorNetwork(msg) - | Event::ErrorSelfNotInGroup(msg) => { - let data2 = CString::new(msg).unwrap_or_default(); - ffi_cb(self, event_id, 0, data2.as_ptr() as uintptr_t); - } - Event::MsgsChanged { chat_id, msg_id } - | Event::IncomingMsg { chat_id, msg_id } - | Event::MsgDelivered { chat_id, msg_id } - | Event::MsgFailed { chat_id, msg_id } - | Event::MsgRead { chat_id, msg_id } => { - ffi_cb( - self, - event_id, - chat_id.to_u32() as uintptr_t, - msg_id.to_u32() as uintptr_t, - ); - } - Event::ChatModified(chat_id) => { - ffi_cb(self, event_id, chat_id.to_u32() as uintptr_t, 0); - } - Event::ContactsChanged(id) | Event::LocationChanged(id) => { - let id = id.unwrap_or_default(); - ffi_cb(self, event_id, id as uintptr_t, 0); - } - Event::ConfigureProgress(progress) | Event::ImexProgress(progress) => { - ffi_cb(self, event_id, progress as uintptr_t, 0); - } - Event::ImexFileWritten(file) => { - let data1 = file.to_c_string().unwrap_or_default(); - ffi_cb(self, event_id, data1.as_ptr() as uintptr_t, 0); - } - Event::SecurejoinInviterProgress { - contact_id, - progress, - } - | Event::SecurejoinJoinerProgress { - contact_id, - progress, - } => { - ffi_cb( - self, - event_id, - contact_id as uintptr_t, - progress as uintptr_t, - ); - } - Event::SecurejoinMemberAdded { - chat_id, - contact_id, - } => { - ffi_cb( - self, - event_id, - chat_id.to_u32() as uintptr_t, - contact_id as uintptr_t, - ); - } + Some(ref ctx) => Ok(ctxfn(ctx)), + None => { + eprintln!("context not open"); + Err(()) } } } + + fn is_open(&self) -> bool { + self.inner.read().unwrap().is_some() + } +} + +macro_rules! with_inner_async { + ($ctx:expr, $name:ident, $block:expr) => {{ + let l = $ctx.inner.clone(); + let lock = l.read().unwrap(); + block_on(async move { + match lock.as_ref() { + Some(ctx) => { + let $name = ctx; + let res = $block.await; + Ok(res) + } + None => { + eprintln!("context not open"); + Err(()) + } + } + }) + }}; +} + +macro_rules! try_inner_async { + ($ctx:expr, $name:ident, $block:expr) => {{ + let l = $ctx.inner.clone(); + let lock = l.read().unwrap(); + block_on(async move { + match lock.as_ref() { + Some(ctx) => { + let $name = ctx; + $block.await + } + None => Err(failure::err_msg("context not open")), + } + }) + }}; } #[no_mangle] pub unsafe extern "C" fn dc_context_new( - cb: Option, userdata: *mut libc::c_void, os_name: *const libc::c_char, ) -> *mut dc_context_t { @@ -226,11 +179,11 @@ pub unsafe extern "C" fn dc_context_new( to_string_lossy(os_name) }; let ffi_ctx = ContextWrapper { - cb, userdata, os_name, - inner: RwLock::new(None), + inner: Arc::new(RwLock::new(None)), }; + Box::into_raw(Box::new(ffi_ctx)) } @@ -268,21 +221,18 @@ pub unsafe extern "C" fn dc_open( return 0; } let ffi_context = &*context; - let rust_cb = move |_ctx: &Context, evt: Event| ffi_context.translate_cb(evt); let ctx = if blobdir.is_null() || *blobdir == 0 { - Context::new( - Box::new(rust_cb), + block_on(Context::new( ffi_context.os_name.clone(), - as_path(dbfile).to_path_buf(), - ) + as_path(dbfile).to_path_buf().into(), + )) } else { - Context::with_blobdir( - Box::new(rust_cb), + block_on(Context::with_blobdir( ffi_context.os_name.clone(), - as_path(dbfile).to_path_buf(), - as_path(blobdir).to_path_buf(), - ) + as_path(dbfile).to_path_buf().into(), + as_path(blobdir).to_path_buf().into(), + )) }; match ctx { Ok(ctx) => { @@ -311,11 +261,7 @@ pub unsafe extern "C" fn dc_is_open(context: *mut dc_context_t) -> libc::c_int { return 0; } let ffi_context = &*context; - let inner_guard = ffi_context.inner.read().unwrap(); - match *inner_guard { - Some(_) => 1, - None => 0, - } + ffi_context.is_open() as libc::c_int } #[no_mangle] @@ -344,12 +290,12 @@ pub unsafe extern "C" fn dc_set_config( match config::Config::from_str(&to_string_lossy(key)) { // When ctx.set_config() fails it already logged the error. // TODO: Context::set_config() should not log this - Ok(key) => ffi_context - .with_inner(|ctx| { - ctx.set_config(key, to_opt_string_lossy(value).as_ref().map(|x| x.as_str())) - .is_ok() as libc::c_int - }) - .unwrap_or(0), + Ok(key) => with_inner_async!(ffi_context, ctx, async move { + ctx.set_config(key, to_opt_string_lossy(value).as_ref().map(|x| x.as_str())) + .await + .is_ok() as libc::c_int + }) + .unwrap_or(0), Err(_) => { ffi_context.warning("dc_set_config(): invalid key"); 0 @@ -368,9 +314,10 @@ pub unsafe extern "C" fn dc_get_config( } let ffi_context = &*context; match config::Config::from_str(&to_string_lossy(key)) { - Ok(key) => ffi_context - .with_inner(|ctx| ctx.get_config(key).unwrap_or_default().strdup()) - .unwrap_or_else(|_| "".strdup()), + Ok(key) => with_inner_async!(ffi_context, ctx, async move { + ctx.get_config(key).await.unwrap_or_default().strdup() + }) + .unwrap_or_else(|_| "".strdup()), Err(_) => { ffi_context.warning("dc_get_config(): invalid key"); "".strdup() @@ -390,9 +337,10 @@ pub unsafe extern "C" fn dc_set_stock_translation( } let msg = to_string_lossy(stock_msg); let ffi_context = &*context; - ffi_context - .with_inner(|ctx| match StockMessage::from_u32(stock_id) { - Some(id) => match ctx.set_stock_translation(id, msg) { + + with_inner_async!(ffi_context, ctx, async move { + match StockMessage::from_u32(stock_id) { + Some(id) => match ctx.set_stock_translation(id, msg).await { Ok(()) => 1, Err(err) => { warn!(ctx, "set_stock_translation failed: {}", err); @@ -403,8 +351,9 @@ pub unsafe extern "C" fn dc_set_stock_translation( warn!(ctx, "invalid stock message id {}", stock_id); 0 } - }) - .unwrap_or(0) + } + }) + .unwrap_or(0) } #[no_mangle] @@ -418,15 +367,17 @@ pub unsafe extern "C" fn dc_set_config_from_qr( } let qr = to_string_lossy(qr); let ffi_context = &*context; - ffi_context - .with_inner(|ctx| match qr::set_config_from_qr(ctx, &qr) { + + with_inner_async!(ffi_context, ctx, async move { + match qr::set_config_from_qr(&ctx, &qr).await { Ok(()) => 1, Err(err) => { error!(ctx, "Failed to create account from QR code: {}", err); 0 } - }) - .unwrap_or(0) + } + }) + .unwrap_or(0) } #[no_mangle] @@ -436,12 +387,15 @@ pub unsafe extern "C" fn dc_get_info(context: *mut dc_context_t) -> *mut libc::c return dc_strdup(ptr::null()); } let ffi_context = &*context; + let guard = ffi_context.inner.read().unwrap(); - let info = match guard.as_ref() { - Some(ref ctx) => ctx.get_info(), - None => context::get_info(), - }; - render_info(info).unwrap_or_default().strdup() + block_on(async move { + let info = match guard.as_ref() { + Some(ref ctx) => ctx.get_info().await, + None => context::get_info(), + }; + render_info(info).unwrap_or_default().strdup() + }) } fn render_info( @@ -468,12 +422,14 @@ pub unsafe extern "C" fn dc_get_oauth2_url( let ffi_context = &*context; let addr = to_string_lossy(addr); let redirect = to_string_lossy(redirect); - ffi_context - .with_inner(|ctx| match oauth2::dc_get_oauth2_url(ctx, addr, redirect) { + + with_inner_async!(ffi_context, ctx, async move { + match oauth2::dc_get_oauth2_url(&ctx, addr, redirect).await { Some(res) => res.strdup(), None => ptr::null_mut(), - }) - .unwrap_or_else(|_| ptr::null_mut()) + } + }) + .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -487,8 +443,9 @@ pub unsafe extern "C" fn dc_configure(context: *mut dc_context_t) { eprintln!("ignoring careless call to dc_configure()"); return; } + // TODO: this is now blocking, maybe change this let ffi_context = &*context; - ffi_context.with_inner(|ctx| ctx.configure()).unwrap_or(()) + with_inner_async!(ffi_context, ctx, async move { ctx.configure().await }).ok(); } #[no_mangle] @@ -498,190 +455,117 @@ pub unsafe extern "C" fn dc_is_configured(context: *mut dc_context_t) -> libc::c return 0; } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| ctx.is_configured() as libc::c_int) - .unwrap_or(0) + + with_inner_async!(ffi_context, ctx, async move { + ctx.is_configured().await as libc::c_int + }) + .unwrap_or(0) } #[no_mangle] -pub unsafe extern "C" fn dc_perform_imap_jobs(context: *mut dc_context_t) { +pub unsafe extern "C" fn dc_context_run(context: *mut dc_context_t) { + eprintln!("dc_context_run"); + if context.is_null() { - eprintln!("ignoring careless call to dc_perform_imap_jobs()"); + eprintln!("ignoring careless call to dc_run()"); return; } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| job::perform_inbox_jobs(ctx)) - .unwrap_or(()) + + with_inner_async!(ffi_context, ctx, { ctx.run() }).unwrap_or(()) +} + +/// Translates the callback from the rust style to the C-style version. +unsafe fn translate_cb(ctx: &ContextWrapper, ffi_cb: Option, event: Event) { + if let Some(ffi_cb) = ffi_cb { + let event_id = event.as_id(); + match event { + Event::Info(msg) + | Event::SmtpConnected(msg) + | Event::ImapConnected(msg) + | Event::SmtpMessageSent(msg) + | Event::ImapMessageDeleted(msg) + | Event::ImapMessageMoved(msg) + | Event::ImapFolderEmptied(msg) + | Event::NewBlobFile(msg) + | Event::DeletedBlobFile(msg) + | Event::Warning(msg) + | Event::Error(msg) + | Event::ErrorNetwork(msg) + | Event::ErrorSelfNotInGroup(msg) => { + let data2 = CString::new(msg).unwrap_or_default(); + ffi_cb(ctx, event_id, 0, data2.as_ptr() as uintptr_t); + } + Event::MsgsChanged { chat_id, msg_id } + | Event::IncomingMsg { chat_id, msg_id } + | Event::MsgDelivered { chat_id, msg_id } + | Event::MsgFailed { chat_id, msg_id } + | Event::MsgRead { chat_id, msg_id } => { + ffi_cb( + ctx, + event_id, + chat_id.to_u32() as uintptr_t, + msg_id.to_u32() as uintptr_t, + ); + } + Event::ChatModified(chat_id) => { + ffi_cb(ctx, event_id, chat_id.to_u32() as uintptr_t, 0); + } + Event::ContactsChanged(id) | Event::LocationChanged(id) => { + let id = id.unwrap_or_default(); + ffi_cb(ctx, event_id, id as uintptr_t, 0); + } + Event::ConfigureProgress(progress) | Event::ImexProgress(progress) => { + ffi_cb(ctx, event_id, progress as uintptr_t, 0); + } + Event::ImexFileWritten(file) => { + let data1 = file.to_c_string().unwrap_or_default(); + ffi_cb(ctx, event_id, data1.as_ptr() as uintptr_t, 0); + } + Event::SecurejoinInviterProgress { + contact_id, + progress, + } + | Event::SecurejoinJoinerProgress { + contact_id, + progress, + } => { + ffi_cb( + ctx, + event_id, + contact_id as uintptr_t, + progress as uintptr_t, + ); + } + Event::SecurejoinMemberAdded { + chat_id, + contact_id, + } => { + ffi_cb( + ctx, + event_id, + chat_id.to_u32() as uintptr_t, + contact_id as uintptr_t, + ); + } + } + } } #[no_mangle] -pub unsafe extern "C" fn dc_perform_imap_fetch(context: *mut dc_context_t) { +pub unsafe extern "C" fn dc_context_shutdown(context: *mut dc_context_t) { if context.is_null() { - eprintln!("ignoring careless call to dc_perform_imap_fetch()"); + eprintln!("ignoring careless call to dc_shutdown()"); return; } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| job::perform_inbox_fetch(ctx)) - .unwrap_or(()) -} -#[no_mangle] -pub unsafe extern "C" fn dc_perform_imap_idle(context: *mut dc_context_t) { - // TODO rename function in co-ordination with UIs - if context.is_null() { - eprintln!("ignoring careless call to dc_perform_imap_idle()"); - return; - } - let ffi_context = &*context; - ffi_context - .with_inner(|ctx| job::perform_inbox_idle(ctx)) - .unwrap_or(()) -} - -#[no_mangle] -pub unsafe extern "C" fn dc_interrupt_imap_idle(context: *mut dc_context_t) { - if context.is_null() { - eprintln!("ignoring careless call to dc_interrupt_imap_idle()"); - return; - } - let ffi_context = &*context; - ffi_context - .with_inner(|ctx| job::interrupt_inbox_idle(ctx)) - .unwrap_or(()) -} - -#[no_mangle] -pub unsafe extern "C" fn dc_perform_mvbox_fetch(context: *mut dc_context_t) { - if context.is_null() { - eprintln!("ignoring careless call to dc_perform_mvbox_fetch()"); - return; - } - let ffi_context = &*context; - ffi_context - .with_inner(|ctx| job::perform_mvbox_fetch(ctx)) - .unwrap_or(()) -} - -#[no_mangle] -pub unsafe extern "C" fn dc_perform_mvbox_jobs(context: *mut dc_context_t) { - if context.is_null() { - eprintln!("ignoring careless call to dc_perform_mvbox_jobs()"); - return; - } - let ffi_context = &*context; - ffi_context - .with_inner(|ctx| job::perform_mvbox_jobs(ctx)) - .unwrap_or(()) -} - -#[no_mangle] -pub unsafe extern "C" fn dc_perform_mvbox_idle(context: *mut dc_context_t) { - if context.is_null() { - eprintln!("ignoring careless call to dc_perform_mvbox_idle()"); - return; - } - let ffi_context = &*context; - ffi_context - .with_inner(|ctx| job::perform_mvbox_idle(ctx)) - .unwrap_or(()) -} - -#[no_mangle] -pub unsafe extern "C" fn dc_interrupt_mvbox_idle(context: *mut dc_context_t) { - if context.is_null() { - eprintln!("ignoring careless call to dc_interrupt_mvbox_idle()"); - return; - } - let ffi_context = &*context; - ffi_context - .with_inner(|ctx| job::interrupt_mvbox_idle(ctx)) - .unwrap_or(()) -} - -#[no_mangle] -pub unsafe extern "C" fn dc_perform_sentbox_fetch(context: *mut dc_context_t) { - if context.is_null() { - eprintln!("ignoring careless call to dc_perform_sentbox_fetch()"); - return; - } - let ffi_context = &*context; - ffi_context - .with_inner(|ctx| job::perform_sentbox_fetch(ctx)) - .unwrap_or(()) -} - -#[no_mangle] -pub unsafe extern "C" fn dc_perform_sentbox_jobs(context: *mut dc_context_t) { - if context.is_null() { - eprintln!("ignoring careless call to dc_perform_sentbox_jobs()"); - return; - } - let ffi_context = &*context; - ffi_context - .with_inner(|ctx| job::perform_sentbox_jobs(ctx)) - .unwrap_or(()) -} - -#[no_mangle] -pub unsafe extern "C" fn dc_perform_sentbox_idle(context: *mut dc_context_t) { - if context.is_null() { - eprintln!("ignoring careless call to dc_perform_sentbox_idle()"); - return; - } - let ffi_context = &*context; - ffi_context - .with_inner(|ctx| job::perform_sentbox_idle(ctx)) - .unwrap_or(()) -} - -#[no_mangle] -pub unsafe extern "C" fn dc_interrupt_sentbox_idle(context: *mut dc_context_t) { - if context.is_null() { - eprintln!("ignoring careless call to dc_interrupt_sentbox_idle()"); - return; - } - let ffi_context = &*context; - ffi_context - .with_inner(|ctx| job::interrupt_sentbox_idle(ctx)) - .unwrap_or(()) -} - -#[no_mangle] -pub unsafe extern "C" fn dc_perform_smtp_jobs(context: *mut dc_context_t) { - if context.is_null() { - eprintln!("ignoring careless call to dc_perform_smtp_jobs()"); - return; - } - let ffi_context = &*context; - ffi_context - .with_inner(|ctx| job::perform_smtp_jobs(ctx)) - .unwrap_or(()) -} - -#[no_mangle] -pub unsafe extern "C" fn dc_perform_smtp_idle(context: *mut dc_context_t) { - if context.is_null() { - eprintln!("ignoring careless call to dc_perform_smtp_idle()"); - return; - } - let ffi_context = &*context; - ffi_context - .with_inner(|ctx| job::perform_smtp_idle(ctx)) - .unwrap_or(()) -} - -#[no_mangle] -pub unsafe extern "C" fn dc_interrupt_smtp_idle(context: *mut dc_context_t) { - if context.is_null() { - eprintln!("ignoring careless call to dc_interrupt_smtp_idle()"); - return; - } - let ffi_context = &*context; - ffi_context - .with_inner(|ctx| job::interrupt_smtp_idle(ctx)) - .unwrap_or(()) + with_inner_async!(ffi_context, ctx, async move { + eprintln!("SHUTDOWN"); + ctx.stop().await; + eprintln!("SHUTDOWN:DONE") + }) + .unwrap_or(()) } #[no_mangle] @@ -691,9 +575,8 @@ pub unsafe extern "C" fn dc_maybe_network(context: *mut dc_context_t) { return; } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| job::maybe_network(ctx)) - .unwrap_or(()) + + with_inner_async!(ffi_context, ctx, async move { ctx.maybe_network().await }).unwrap_or(()) } #[no_mangle] @@ -708,21 +591,20 @@ pub unsafe extern "C" fn dc_preconfigure_keypair( return 0; } let ffi_context = &*context; - ffi_context - .try_inner(|ctx| { - let addr = dc_tools::EmailAddress::new(&to_string_lossy(addr))?; - let public = key::SignedPublicKey::from_base64(&to_string_lossy(public_data))?; - let secret = key::SignedSecretKey::from_base64(&to_string_lossy(secret_data))?; - let keypair = key::KeyPair { - addr, - public, - secret, - }; - key::store_self_keypair(ctx, &keypair, key::KeyPairUse::Default)?; - Ok(1) - }) - .log_err(ffi_context, "Failed to save keypair") - .unwrap_or(0) + try_inner_async!(ffi_context, ctx, async move { + let addr = dc_tools::EmailAddress::new(&to_string_lossy(addr))?; + let public = key::SignedPublicKey::from_base64(&to_string_lossy(public_data))?; + let secret = key::SignedSecretKey::from_base64(&to_string_lossy(secret_data))?; + let keypair = key::KeyPair { + addr, + public, + secret, + }; + key::store_self_keypair(&ctx, &keypair, key::KeyPairUse::Default).await?; + Ok(1) + }) + .log_err(ffi_context, "Failed to save keypair") + .unwrap_or(0) } #[no_mangle] @@ -740,22 +622,24 @@ pub unsafe extern "C" fn dc_get_chatlist( let qs = to_opt_string_lossy(query_str); let qi = if query_id == 0 { None } else { Some(query_id) }; - ffi_context - .with_inner(|ctx| { - match chatlist::Chatlist::try_load( - ctx, - flags as usize, - qs.as_ref().map(|x| x.as_str()), - qi, - ) { - Ok(list) => { - let ffi_list = ChatlistWrapper { context, list }; - Box::into_raw(Box::new(ffi_list)) - } - Err(_) => ptr::null_mut(), + + with_inner_async!(ffi_context, ctx, async move { + match chatlist::Chatlist::try_load( + &ctx, + flags as usize, + qs.as_ref().map(|x| x.as_str()), + qi, + ) + .await + { + Ok(list) => { + let ffi_list = ChatlistWrapper { context, list }; + Box::into_raw(Box::new(ffi_list)) } - }) - .unwrap_or_else(|_| ptr::null_mut()) + Err(_) => ptr::null_mut(), + } + }) + .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -765,14 +649,15 @@ pub unsafe extern "C" fn dc_create_chat_by_msg_id(context: *mut dc_context_t, ms return 0; } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| { - chat::create_by_msg_id(ctx, MsgId::new(msg_id)) - .log_err(ffi_context, "Failed to create chat from msg_id") - .map(|id| id.to_u32()) - .unwrap_or(0) - }) - .unwrap_or(0) + + with_inner_async!(ffi_context, ctx, async move { + chat::create_by_msg_id(&ctx, MsgId::new(msg_id)) + .await + .log_err(ffi_context, "Failed to create chat from msg_id") + .map(|id| id.to_u32()) + .unwrap_or(0) + }) + .unwrap_or(0) } #[no_mangle] @@ -785,14 +670,15 @@ pub unsafe extern "C" fn dc_create_chat_by_contact_id( return 0; } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| { - chat::create_by_contact_id(ctx, contact_id) - .log_err(ffi_context, "Failed to create chat from contact_id") - .map(|id| id.to_u32()) - .unwrap_or(0) - }) - .unwrap_or(0) + + with_inner_async!(ffi_context, ctx, async move { + chat::create_by_contact_id(&ctx, contact_id) + .await + .log_err(ffi_context, "Failed to create chat from contact_id") + .map(|id| id.to_u32()) + .unwrap_or(0) + }) + .unwrap_or(0) } #[no_mangle] @@ -805,14 +691,15 @@ pub unsafe extern "C" fn dc_get_chat_id_by_contact_id( return 0; } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| { - chat::get_by_contact_id(ctx, contact_id) - .log_err(ffi_context, "Failed to get chat for contact_id") - .map(|id| id.to_u32()) - .unwrap_or(0) - }) - .unwrap_or(0) + + with_inner_async!(ffi_context, ctx, async move { + chat::get_by_contact_id(&ctx, contact_id) + .await + .log_err(ffi_context, "Failed to get chat for contact_id") + .map(|id| id.to_u32()) + .unwrap_or(0) + }) + .unwrap_or(0) } #[no_mangle] @@ -827,13 +714,14 @@ pub unsafe extern "C" fn dc_prepare_msg( } let ffi_context = &mut *context; let ffi_msg: &mut MessageWrapper = &mut *msg; - ffi_context - .with_inner(|ctx| { - chat::prepare_msg(ctx, ChatId::new(chat_id), &mut ffi_msg.message) - .unwrap_or_log_default(ctx, "Failed to prepare message") - }) - .map(|msg_id| msg_id.to_u32()) - .unwrap_or(0) + + with_inner_async!(ffi_context, ctx, async move { + chat::prepare_msg(&ctx, ChatId::new(chat_id), &mut ffi_msg.message) + .await + .unwrap_or_log_default(&ctx, "Failed to prepare message") + }) + .map(|msg_id| msg_id.to_u32()) + .unwrap_or(0) } #[no_mangle] @@ -848,13 +736,14 @@ pub unsafe extern "C" fn dc_send_msg( } let ffi_context = &mut *context; let ffi_msg = &mut *msg; - ffi_context - .with_inner(|ctx| { - chat::send_msg(ctx, ChatId::new(chat_id), &mut ffi_msg.message) - .unwrap_or_log_default(ctx, "Failed to send message") - }) - .map(|msg_id| msg_id.to_u32()) - .unwrap_or(0) + + with_inner_async!(ffi_context, ctx, async move { + chat::send_msg(&ctx, ChatId::new(chat_id), &mut ffi_msg.message) + .await + .unwrap_or_log_default(&ctx, "Failed to send message") + }) + .map(|msg_id| msg_id.to_u32()) + .unwrap_or(0) } #[no_mangle] @@ -869,13 +758,14 @@ pub unsafe extern "C" fn dc_send_text_msg( } let ffi_context = &*context; let text_to_send = to_string_lossy(text_to_send); - ffi_context - .with_inner(|ctx| { - chat::send_text_msg(ctx, ChatId::new(chat_id), text_to_send) - .map(|msg_id| msg_id.to_u32()) - .unwrap_or_log_default(ctx, "Failed to send text message") - }) - .unwrap_or(0) + + with_inner_async!(ffi_context, ctx, async move { + chat::send_text_msg(&ctx, ChatId::new(chat_id), text_to_send) + .await + .map(|msg_id| msg_id.to_u32()) + .unwrap_or_log_default(&ctx, "Failed to send text message") + }) + .unwrap_or(0) } #[no_mangle] @@ -895,9 +785,8 @@ pub unsafe extern "C" fn dc_set_draft( let ffi_msg: &mut MessageWrapper = &mut *msg; Some(&mut ffi_msg.message) }; - ffi_context - .with_inner(|ctx| ChatId::new(chat_id).set_draft(ctx, msg)) - .unwrap_or(()) + + with_inner_async!(ffi_context, ctx, ChatId::new(chat_id).set_draft(&ctx, msg)).unwrap_or(()) } #[no_mangle] @@ -917,17 +806,18 @@ pub unsafe extern "C" fn dc_add_device_msg( let ffi_msg: &mut MessageWrapper = &mut *msg; Some(&mut ffi_msg.message) }; - ffi_context - .with_inner(|ctx| { - chat::add_device_msg( - ctx, - to_opt_string_lossy(label).as_ref().map(|x| x.as_str()), - msg, - ) - .unwrap_or_log_default(ctx, "Failed to add device message") - }) - .map(|msg_id| msg_id.to_u32()) - .unwrap_or(0) + + with_inner_async!(ffi_context, ctx, async move { + chat::add_device_msg( + &ctx, + to_opt_string_lossy(label).as_ref().map(|x| x.as_str()), + msg, + ) + .await + .unwrap_or_log_default(&ctx, "Failed to add device message") + }) + .map(|msg_id| msg_id.to_u32()) + .unwrap_or(0) } #[no_mangle] @@ -937,12 +827,13 @@ pub unsafe extern "C" fn dc_update_device_chats(context: *mut dc_context_t) { return; } let ffi_context = &mut *context; - ffi_context - .with_inner(|ctx| { - ctx.update_device_chats() - .unwrap_or_log_default(ctx, "Failed to add device message") - }) - .unwrap_or(()) + + with_inner_async!(ffi_context, ctx, async move { + ctx.update_device_chats() + .await + .unwrap_or_log_default(&ctx, "Failed to add device message") + }) + .unwrap_or(()) } #[no_mangle] @@ -955,12 +846,13 @@ pub unsafe extern "C" fn dc_was_device_msg_ever_added( return 0; } let ffi_context = &mut *context; - ffi_context - .with_inner(|ctx| { - chat::was_device_msg_ever_added(ctx, &to_string_lossy(label)).unwrap_or(false) - as libc::c_int - }) - .unwrap_or(0) + + with_inner_async!(ffi_context, ctx, async move { + chat::was_device_msg_ever_added(&ctx, &to_string_lossy(label)) + .await + .unwrap_or(false) as libc::c_int + }) + .unwrap_or(0) } #[no_mangle] @@ -970,8 +862,9 @@ pub unsafe extern "C" fn dc_get_draft(context: *mut dc_context_t, chat_id: u32) return ptr::null_mut(); // NULL explicitly defined as "no draft" } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| match ChatId::new(chat_id).get_draft(ctx) { + + with_inner_async!(ffi_context, ctx, async move { + match ChatId::new(chat_id).get_draft(&ctx).await { Ok(Some(draft)) => { let ffi_msg = MessageWrapper { context, @@ -984,8 +877,9 @@ pub unsafe extern "C" fn dc_get_draft(context: *mut dc_context_t, chat_id: u32) error!(ctx, "Failed to get draft for chat #{}: {}", chat_id, err); ptr::null_mut() } - }) - .unwrap_or_else(|_| ptr::null_mut()) + } + }) + .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -1005,17 +899,18 @@ pub unsafe extern "C" fn dc_get_chat_msgs( } else { Some(MsgId::new(marker1before)) }; - ffi_context - .with_inner(|ctx| { - let arr = dc_array_t::from( - chat::get_chat_msgs(ctx, ChatId::new(chat_id), flags, marker_flag) - .iter() - .map(|msg_id| msg_id.to_u32()) - .collect::>(), - ); - Box::into_raw(Box::new(arr)) - }) - .unwrap_or_else(|_| ptr::null_mut()) + + with_inner_async!(ffi_context, ctx, async move { + let arr = dc_array_t::from( + chat::get_chat_msgs(&ctx, ChatId::new(chat_id), flags, marker_flag) + .await + .iter() + .map(|msg_id| msg_id.to_u32()) + .collect::>(), + ); + Box::into_raw(Box::new(arr)) + }) + .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -1025,9 +920,11 @@ pub unsafe extern "C" fn dc_get_msg_cnt(context: *mut dc_context_t, chat_id: u32 return 0; } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| ChatId::new(chat_id).get_msg_cnt(ctx) as libc::c_int) - .unwrap_or(0) + + with_inner_async!(ffi_context, ctx, async move { + ChatId::new(chat_id).get_msg_cnt(&ctx).await as libc::c_int + }) + .unwrap_or(0) } #[no_mangle] @@ -1040,9 +937,11 @@ pub unsafe extern "C" fn dc_get_fresh_msg_cnt( return 0; } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| ChatId::new(chat_id).get_fresh_msg_cnt(ctx) as libc::c_int) - .unwrap_or(0) + + with_inner_async!(ffi_context, ctx, async move { + ChatId::new(chat_id).get_fresh_msg_cnt(&ctx).await as libc::c_int + }) + .unwrap_or(0) } #[no_mangle] @@ -1054,17 +953,18 @@ pub unsafe extern "C" fn dc_get_fresh_msgs( return ptr::null_mut(); } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| { - let arr = dc_array_t::from( - ctx.get_fresh_msgs() - .iter() - .map(|msg_id| msg_id.to_u32()) - .collect::>(), - ); - Box::into_raw(Box::new(arr)) - }) - .unwrap_or_else(|_| ptr::null_mut()) + + with_inner_async!(ffi_context, ctx, async move { + let arr = dc_array_t::from( + ctx.get_fresh_msgs() + .await + .iter() + .map(|msg_id| msg_id.to_u32()) + .collect::>(), + ); + Box::into_raw(Box::new(arr)) + }) + .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -1074,13 +974,14 @@ pub unsafe extern "C" fn dc_marknoticed_chat(context: *mut dc_context_t, chat_id return; } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| { - chat::marknoticed_chat(ctx, ChatId::new(chat_id)) - .log_err(ffi_context, "Failed marknoticed chat") - .unwrap_or(()) - }) - .unwrap_or(()) + + with_inner_async!(ffi_context, ctx, async move { + chat::marknoticed_chat(&ctx, ChatId::new(chat_id)) + .await + .log_err(ffi_context, "Failed marknoticed chat") + .unwrap_or(()) + }) + .unwrap_or(()) } #[no_mangle] @@ -1090,13 +991,14 @@ pub unsafe extern "C" fn dc_marknoticed_all_chats(context: *mut dc_context_t) { return; } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| { - chat::marknoticed_all_chats(ctx) - .log_err(ffi_context, "Failed marknoticed all chats") - .unwrap_or(()) - }) - .unwrap_or(()) + + with_inner_async!(ffi_context, ctx, async move { + chat::marknoticed_all_chats(&ctx) + .await + .log_err(ffi_context, "Failed marknoticed all chats") + .unwrap_or(()) + }) + .unwrap_or(()) } fn from_prim(s: S) -> Option @@ -1125,23 +1027,24 @@ pub unsafe extern "C" fn dc_get_chat_media( from_prim(or_msg_type2).expect(&format!("incorrect or_msg_type2 = {}", or_msg_type2)); let or_msg_type3 = from_prim(or_msg_type3).expect(&format!("incorrect or_msg_type3 = {}", or_msg_type3)); - ffi_context - .with_inner(|ctx| { - let arr = dc_array_t::from( - chat::get_chat_media( - ctx, - ChatId::new(chat_id), - msg_type, - or_msg_type2, - or_msg_type3, - ) - .iter() - .map(|msg_id| msg_id.to_u32()) - .collect::>(), - ); - Box::into_raw(Box::new(arr)) - }) - .unwrap_or_else(|_| ptr::null_mut()) + + with_inner_async!(ffi_context, ctx, async move { + let arr = dc_array_t::from( + chat::get_chat_media( + &ctx, + ChatId::new(chat_id), + msg_type, + or_msg_type2, + or_msg_type3, + ) + .await + .iter() + .map(|msg_id| msg_id.to_u32()) + .collect::>(), + ); + Box::into_raw(Box::new(arr)) + }) + .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -1169,20 +1072,21 @@ pub unsafe extern "C" fn dc_get_next_media( from_prim(or_msg_type2).expect(&format!("incorrect or_msg_type2 = {}", or_msg_type2)); let or_msg_type3 = from_prim(or_msg_type3).expect(&format!("incorrect or_msg_type3 = {}", or_msg_type3)); - ffi_context - .with_inner(|ctx| { - chat::get_next_media( - ctx, - MsgId::new(msg_id), - direction, - msg_type, - or_msg_type2, - or_msg_type3, - ) - .map(|msg_id| msg_id.to_u32()) - .unwrap_or(0) - }) + + with_inner_async!(ffi_context, ctx, async move { + chat::get_next_media( + &ctx, + MsgId::new(msg_id), + direction, + msg_type, + or_msg_type2, + or_msg_type3, + ) + .await + .map(|msg_id| msg_id.to_u32()) .unwrap_or(0) + }) + .unwrap_or(0) } #[no_mangle] @@ -1207,14 +1111,15 @@ pub unsafe extern "C" fn dc_set_chat_visibility( return; } }; - ffi_context - .with_inner(|ctx| { - ChatId::new(chat_id) - .set_visibility(ctx, visibility) - .log_err(ffi_context, "Failed setting chat visibility") - .unwrap_or(()) - }) - .unwrap_or(()) + + with_inner_async!(ffi_context, ctx, async move { + ChatId::new(chat_id) + .set_visibility(&ctx, visibility) + .await + .log_err(ffi_context, "Failed setting chat visibility") + .unwrap_or(()) + }) + .unwrap_or(()) } #[no_mangle] @@ -1224,14 +1129,15 @@ pub unsafe extern "C" fn dc_delete_chat(context: *mut dc_context_t, chat_id: u32 return; } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| { - ChatId::new(chat_id) - .delete(ctx) - .log_err(ffi_context, "Failed chat delete") - .unwrap_or(()) - }) - .unwrap_or(()) + + with_inner_async!(ffi_context, ctx, async move { + ChatId::new(chat_id) + .delete(&ctx) + .await + .log_err(ffi_context, "Failed chat delete") + .unwrap_or(()) + }) + .unwrap_or(()) } #[no_mangle] @@ -1244,12 +1150,12 @@ pub unsafe extern "C" fn dc_get_chat_contacts( return ptr::null_mut(); } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| { - let arr = dc_array_t::from(chat::get_chat_contacts(ctx, ChatId::new(chat_id))); - Box::into_raw(Box::new(arr)) - }) - .unwrap_or_else(|_| ptr::null_mut()) + + with_inner_async!(ffi_context, ctx, async move { + let arr = dc_array_t::from(chat::get_chat_contacts(&ctx, ChatId::new(chat_id)).await); + Box::into_raw(Box::new(arr)) + }) + .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -1263,17 +1169,18 @@ pub unsafe extern "C" fn dc_search_msgs( return ptr::null_mut(); } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| { - let arr = dc_array_t::from( - ctx.search_msgs(ChatId::new(chat_id), to_string_lossy(query)) - .iter() - .map(|msg_id| msg_id.to_u32()) - .collect::>(), - ); - Box::into_raw(Box::new(arr)) - }) - .unwrap_or_else(|_| ptr::null_mut()) + + with_inner_async!(ffi_context, ctx, async move { + let arr = dc_array_t::from( + ctx.search_msgs(ChatId::new(chat_id), to_string_lossy(query)) + .await + .iter() + .map(|msg_id| msg_id.to_u32()) + .collect::>(), + ); + Box::into_raw(Box::new(arr)) + }) + .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -1283,17 +1190,17 @@ pub unsafe extern "C" fn dc_get_chat(context: *mut dc_context_t, chat_id: u32) - return ptr::null_mut(); } let ffi_context = &*context; - ffi_context - .with_inner( - |ctx| match chat::Chat::load_from_db(ctx, ChatId::new(chat_id)) { - Ok(chat) => { - let ffi_chat = ChatWrapper { context, chat }; - Box::into_raw(Box::new(ffi_chat)) - } - Err(_) => ptr::null_mut(), - }, - ) - .unwrap_or_else(|_| ptr::null_mut()) + + with_inner_async!(ffi_context, ctx, async move { + match chat::Chat::load_from_db(&ctx, ChatId::new(chat_id)).await { + Ok(chat) => { + let ffi_chat = ChatWrapper { context, chat }; + Box::into_raw(Box::new(ffi_chat)) + } + Err(_) => ptr::null_mut(), + } + }) + .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -1312,14 +1219,15 @@ pub unsafe extern "C" fn dc_create_group_chat( } else { return 0; }; - ffi_context - .with_inner(|ctx| { - chat::create_group_chat(ctx, verified, to_string_lossy(name)) - .log_err(ffi_context, "Failed to create group chat") - .map(|id| id.to_u32()) - .unwrap_or(0) - }) - .unwrap_or(0) + + with_inner_async!(ffi_context, ctx, async move { + chat::create_group_chat(&ctx, verified, to_string_lossy(name)) + .await + .log_err(ffi_context, "Failed to create group chat") + .map(|id| id.to_u32()) + .unwrap_or(0) + }) + .unwrap_or(0) } #[no_mangle] @@ -1333,10 +1241,14 @@ pub unsafe extern "C" fn dc_is_contact_in_chat( return 0; } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| chat::is_contact_in_chat(ctx, ChatId::new(chat_id), contact_id)) - .unwrap_or_default() - .into() + + with_inner_async!( + ffi_context, + ctx, + chat::is_contact_in_chat(&ctx, ChatId::new(chat_id), contact_id) + ) + .unwrap_or_default() + .into() } #[no_mangle] @@ -1350,11 +1262,11 @@ pub unsafe extern "C" fn dc_add_contact_to_chat( return 0; } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| { - chat::add_contact_to_chat(ctx, ChatId::new(chat_id), contact_id) as libc::c_int - }) - .unwrap_or(0) + + with_inner_async!(ffi_context, ctx, async move { + chat::add_contact_to_chat(&ctx, ChatId::new(chat_id), contact_id).await as libc::c_int + }) + .unwrap_or(0) } #[no_mangle] @@ -1368,13 +1280,14 @@ pub unsafe extern "C" fn dc_remove_contact_from_chat( return 0; } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| { - chat::remove_contact_from_chat(ctx, ChatId::new(chat_id), contact_id) - .map(|_| 1) - .unwrap_or_log_default(ctx, "Failed to remove contact") - }) - .unwrap_or(0) + + with_inner_async!(ffi_context, ctx, async move { + chat::remove_contact_from_chat(&ctx, ChatId::new(chat_id), contact_id) + .await + .map(|_| 1) + .unwrap_or_log_default(&ctx, "Failed to remove contact") + }) + .unwrap_or(0) } #[no_mangle] @@ -1388,13 +1301,14 @@ pub unsafe extern "C" fn dc_set_chat_name( return 0; } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| { - chat::set_chat_name(ctx, ChatId::new(chat_id), to_string_lossy(name)) - .map(|_| 1) - .unwrap_or_log_default(ctx, "Failed to set chat name") - }) - .unwrap_or(0) + + with_inner_async!(ffi_context, ctx, async move { + chat::set_chat_name(&ctx, ChatId::new(chat_id), to_string_lossy(name)) + .await + .map(|_| 1) + .unwrap_or_log_default(&ctx, "Failed to set chat name") + }) + .unwrap_or(0) } #[no_mangle] @@ -1408,13 +1322,14 @@ pub unsafe extern "C" fn dc_set_chat_profile_image( return 0; } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| { - chat::set_chat_profile_image(ctx, ChatId::new(chat_id), to_string_lossy(image)) - .map(|_| 1) - .unwrap_or_log_default(ctx, "Failed to set profile image") - }) - .unwrap_or(0) + + with_inner_async!(ffi_context, ctx, async move { + chat::set_chat_profile_image(&ctx, ChatId::new(chat_id), to_string_lossy(image)) + .await + .map(|_| 1) + .unwrap_or_log_default(&ctx, "Failed to set profile image") + }) + .unwrap_or(0) } #[no_mangle] @@ -1439,13 +1354,14 @@ pub unsafe extern "C" fn dc_set_chat_mute_duration( return 0; } }; - ffi_context - .with_inner(|ctx| { - chat::set_muted(ctx, ChatId::new(chat_id), muteDuration) - .map(|_| 1) - .unwrap_or_log_default(ctx, "Failed to set mute duration") - }) - .unwrap_or(0) + + with_inner_async!(ffi_context, ctx, async move { + chat::set_muted(&ctx, ChatId::new(chat_id), muteDuration) + .await + .map(|_| 1) + .unwrap_or_log_default(&ctx, "Failed to set mute duration") + }) + .unwrap_or(0) } #[no_mangle] @@ -1458,9 +1374,14 @@ pub unsafe extern "C" fn dc_get_msg_info( return dc_strdup(ptr::null()); } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| message::get_msg_info(ctx, MsgId::new(msg_id)).strdup()) - .unwrap_or_else(|_| ptr::null_mut()) + + with_inner_async!( + ffi_context, + ctx, + message::get_msg_info(&ctx, MsgId::new(msg_id)) + ) + .map(|s| s.strdup()) + .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -1473,13 +1394,14 @@ pub unsafe extern "C" fn dc_get_mime_headers( return ptr::null_mut(); // NULL explicitly defined as "no mime headers" } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| { - message::get_mime_headers(ctx, MsgId::new(msg_id)) - .map(|s| s.strdup()) - .unwrap_or_else(ptr::null_mut) - }) - .unwrap_or_else(|_| ptr::null_mut()) + + with_inner_async!(ffi_context, ctx, async move { + message::get_mime_headers(&ctx, MsgId::new(msg_id)) + .await + .map(|s| s.strdup()) + .unwrap_or_else(ptr::null_mut) + }) + .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -1494,9 +1416,8 @@ pub unsafe extern "C" fn dc_delete_msgs( } let ffi_context = &*context; let msg_ids = convert_and_prune_message_ids(msg_ids, msg_cnt); - ffi_context - .with_inner(|ctx| message::delete_msgs(ctx, &msg_ids[..])) - .unwrap_or(()) + + with_inner_async!(ffi_context, ctx, message::delete_msgs(&ctx, &msg_ids)).unwrap_or(()) } #[no_mangle] @@ -1506,9 +1427,8 @@ pub unsafe extern "C" fn dc_empty_server(context: *mut dc_context_t, flags: u32) return; } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| message::dc_empty_server(ctx, flags)) - .unwrap_or(()) + + with_inner_async!(ffi_context, ctx, message::dc_empty_server(&ctx, flags)).unwrap_or(()) } #[no_mangle] @@ -1528,12 +1448,13 @@ pub unsafe extern "C" fn dc_forward_msgs( } let msg_ids = convert_and_prune_message_ids(msg_ids, msg_cnt); let ffi_context = &*context; - ffi_context - .with_inner(|ctx| { - chat::forward_msgs(ctx, &msg_ids[..], ChatId::new(chat_id)) - .unwrap_or_log_default(ctx, "Failed to forward message") - }) - .unwrap_or_default() + + with_inner_async!(ffi_context, ctx, async move { + chat::forward_msgs(&ctx, &msg_ids[..], ChatId::new(chat_id)) + .await + .unwrap_or_log_default(&ctx, "Failed to forward message") + }) + .unwrap_or_default() } #[no_mangle] @@ -1543,9 +1464,8 @@ pub unsafe extern "C" fn dc_marknoticed_contact(context: *mut dc_context_t, cont return; } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| Contact::mark_noticed(ctx, contact_id)) - .unwrap_or(()) + + with_inner_async!(ffi_context, ctx, Contact::mark_noticed(&ctx, contact_id)).unwrap_or(()) } #[no_mangle] @@ -1560,9 +1480,8 @@ pub unsafe extern "C" fn dc_markseen_msgs( } let msg_ids = convert_and_prune_message_ids(msg_ids, msg_cnt); let ffi_context = &*context; - ffi_context - .with_inner(|ctx| message::markseen_msgs(ctx, &msg_ids[..])) - .ok(); + + with_inner_async!(ffi_context, ctx, message::markseen_msgs(&ctx, msg_ids)).ok(); } #[no_mangle] @@ -1578,9 +1497,13 @@ pub unsafe extern "C" fn dc_star_msgs( } let msg_ids = convert_and_prune_message_ids(msg_ids, msg_cnt); let ffi_context = &*context; - ffi_context - .with_inner(|ctx| message::star_msgs(ctx, &msg_ids[..], star == 1)) - .ok(); + + with_inner_async!( + ffi_context, + ctx, + message::star_msgs(&ctx, msg_ids, star == 1) + ) + .ok(); } #[no_mangle] @@ -1590,31 +1513,31 @@ pub unsafe extern "C" fn dc_get_msg(context: *mut dc_context_t, msg_id: u32) -> return ptr::null_mut(); } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| { - let message = match message::Message::load_from_db(ctx, MsgId::new(msg_id)) { - Ok(msg) => msg, - Err(e) => { - if msg_id <= constants::DC_MSG_ID_LAST_SPECIAL { - // C-core API returns empty messages, do the same - warn!( - ctx, - "dc_get_msg called with special msg_id={}, returning empty msg", msg_id - ); - message::Message::default() - } else { - error!( - ctx, - "dc_get_msg could not retrieve msg_id {}: {}", msg_id, e - ); - return ptr::null_mut(); - } + + with_inner_async!(ffi_context, ctx, async move { + let message = match message::Message::load_from_db(&ctx, MsgId::new(msg_id)).await { + Ok(msg) => msg, + Err(e) => { + if msg_id <= constants::DC_MSG_ID_LAST_SPECIAL { + // C-core API returns empty messages, do the same + warn!( + &ctx, + "dc_get_msg called with special msg_id={}, returning empty msg", msg_id + ); + message::Message::default() + } else { + error!( + &ctx, + "dc_get_msg could not retrieve msg_id {}: {}", msg_id, e + ); + return ptr::null_mut(); } - }; - let ffi_msg = MessageWrapper { context, message }; - Box::into_raw(Box::new(ffi_msg)) - }) - .unwrap_or_else(|_| ptr::null_mut()) + } + }; + let ffi_msg = MessageWrapper { context, message }; + Box::into_raw(Box::new(ffi_msg)) + }) + .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -1637,9 +1560,13 @@ pub unsafe extern "C" fn dc_lookup_contact_id_by_addr( return 0; } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| Contact::lookup_id_by_addr(ctx, to_string_lossy(addr))) - .unwrap_or(0) + + with_inner_async!( + ffi_context, + ctx, + Contact::lookup_id_by_addr(&ctx, to_string_lossy(addr)) + ) + .unwrap_or(0) } #[no_mangle] @@ -1654,14 +1581,14 @@ pub unsafe extern "C" fn dc_create_contact( } let ffi_context = &*context; let name = to_string_lossy(name); - ffi_context - .with_inner( - |ctx| match Contact::create(ctx, name, to_string_lossy(addr)) { - Ok(id) => id, - Err(_) => 0, - }, - ) - .unwrap_or(0) + + with_inner_async!(ffi_context, ctx, async move { + match Contact::create(&ctx, name, to_string_lossy(addr)).await { + Ok(id) => id, + Err(_) => 0, + } + }) + .unwrap_or(0) } #[no_mangle] @@ -1674,14 +1601,14 @@ pub unsafe extern "C" fn dc_add_address_book( return 0; } let ffi_context = &*context; - ffi_context - .with_inner( - |ctx| match Contact::add_address_book(ctx, to_string_lossy(addr_book)) { - Ok(cnt) => cnt as libc::c_int, - Err(_) => 0, - }, - ) - .unwrap_or(0) + + with_inner_async!(ffi_context, ctx, async move { + match Contact::add_address_book(&ctx, to_string_lossy(addr_book)).await { + Ok(cnt) => cnt as libc::c_int, + Err(_) => 0, + } + }) + .unwrap_or(0) } #[no_mangle] @@ -1696,12 +1623,14 @@ pub unsafe extern "C" fn dc_get_contacts( } let ffi_context = &*context; let query = to_opt_string_lossy(query); - ffi_context - .with_inner(|ctx| match Contact::get_all(ctx, flags, query) { + + with_inner_async!(ffi_context, ctx, async move { + match Contact::get_all(&ctx, flags, query).await { Ok(contacts) => Box::into_raw(Box::new(dc_array_t::from(contacts))), Err(_) => ptr::null_mut(), - }) - .unwrap_or_else(|_| ptr::null_mut()) + } + }) + .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -1711,9 +1640,8 @@ pub unsafe extern "C" fn dc_get_blocked_cnt(context: *mut dc_context_t) -> libc: return 0; } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| Contact::get_blocked_cnt(ctx) as libc::c_int) - .unwrap_or(0) + + with_inner_async!(ffi_context, ctx, Contact::get_blocked_cnt(&ctx)).unwrap_or(0) as libc::c_int } #[no_mangle] @@ -1725,9 +1653,13 @@ pub unsafe extern "C" fn dc_get_blocked_contacts( return ptr::null_mut(); } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| Box::into_raw(Box::new(dc_array_t::from(Contact::get_all_blocked(ctx))))) - .unwrap_or_else(|_| ptr::null_mut()) + + with_inner_async!(ffi_context, ctx, async move { + Box::into_raw(Box::new(dc_array_t::from( + Contact::get_all_blocked(&ctx).await, + ))) + }) + .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -1741,15 +1673,14 @@ pub unsafe extern "C" fn dc_block_contact( return; } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| { - if block == 0 { - Contact::unblock(ctx, contact_id); - } else { - Contact::block(ctx, contact_id); - } - }) - .ok(); + with_inner_async!(ffi_context, ctx, async move { + if block == 0 { + Contact::unblock(&ctx, contact_id).await; + } else { + Contact::block(&ctx, contact_id).await; + } + }) + .ok(); } #[no_mangle] @@ -1762,16 +1693,17 @@ pub unsafe extern "C" fn dc_get_contact_encrinfo( return "".strdup(); } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| { - Contact::get_encrinfo(ctx, contact_id) - .map(|s| s.strdup()) - .unwrap_or_else(|e| { - error!(ctx, "{}", e); - ptr::null_mut() - }) - }) - .unwrap_or_else(|_| "".strdup()) + + with_inner_async!(ffi_context, ctx, async move { + Contact::get_encrinfo(&ctx, contact_id) + .await + .map(|s| s.strdup()) + .unwrap_or_else(|e| { + error!(&ctx, "{}", e); + ptr::null_mut() + }) + }) + .unwrap_or_else(|_| "".strdup()) } #[no_mangle] @@ -1784,12 +1716,14 @@ pub unsafe extern "C" fn dc_delete_contact( return 0; } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| match Contact::delete(ctx, contact_id) { + + with_inner_async!(ffi_context, ctx, async move { + match Contact::delete(&ctx, contact_id).await { Ok(_) => 1, Err(_) => 0, - }) - .unwrap_or(0) + } + }) + .unwrap_or(0) } #[no_mangle] @@ -1802,13 +1736,14 @@ pub unsafe extern "C" fn dc_get_contact( return ptr::null_mut(); } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| { - Contact::get_by_id(ctx, contact_id) - .map(|contact| Box::into_raw(Box::new(ContactWrapper { context, contact }))) - .unwrap_or_else(|_| ptr::null_mut()) - }) - .unwrap_or_else(|_| ptr::null_mut()) + + with_inner_async!(ffi_context, ctx, async move { + Contact::get_by_id(&ctx, contact_id) + .await + .map(|contact| Box::into_raw(Box::new(ContactWrapper { context, contact }))) + .unwrap_or_else(|_| ptr::null_mut()) + }) + .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -1830,10 +1765,14 @@ pub unsafe extern "C" fn dc_imex( } }; + // TODO: this is now blocking, figure out if that is okay let ffi_context = &*context; - ffi_context - .with_inner(|ctx| imex::imex(ctx, what, to_opt_string_lossy(param1))) - .ok(); + with_inner_async!( + ffi_context, + ctx, + imex::imex(&ctx, what, to_opt_string_lossy(param1)) + ) + .ok(); } #[no_mangle] @@ -1846,17 +1785,19 @@ pub unsafe extern "C" fn dc_imex_has_backup( return ptr::null_mut(); // NULL explicitly defined as "has no backup" } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| match imex::has_backup(ctx, to_string_lossy(dir)) { + + with_inner_async!(ffi_context, ctx, async move { + match imex::has_backup(&ctx, to_string_lossy(dir)).await { Ok(res) => res.strdup(), Err(err) => { // do not bubble up error to the user, // the ui will expect that the file does not exist or cannot be accessed - warn!(ctx, "dc_imex_has_backup: {}", err); + warn!(&ctx, "dc_imex_has_backup: {}", err); ptr::null_mut() } - }) - .unwrap_or_else(|_| ptr::null_mut()) + } + }) + .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -1866,15 +1807,17 @@ pub unsafe extern "C" fn dc_initiate_key_transfer(context: *mut dc_context_t) -> return ptr::null_mut(); // NULL explicitly defined as "error" } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| match imex::initiate_key_transfer(ctx) { + + with_inner_async!(ffi_context, ctx, async move { + match imex::initiate_key_transfer(&ctx).await { Ok(res) => res.strdup(), Err(err) => { - error!(ctx, "dc_initiate_key_transfer(): {}", err); + error!(&ctx, "dc_initiate_key_transfer(): {}", err); ptr::null_mut() } - }) - .unwrap_or_else(|_| ptr::null_mut()) + } + }) + .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -1891,18 +1834,19 @@ pub unsafe extern "C" fn dc_continue_key_transfer( return 0; } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| { - match imex::continue_key_transfer(ctx, MsgId::new(msg_id), &to_string_lossy(setup_code)) - { - Ok(()) => 1, - Err(err) => { - error!(ctx, "dc_continue_key_transfer: {}", err); - 0 - } + + with_inner_async!(ffi_context, ctx, async move { + match imex::continue_key_transfer(&ctx, MsgId::new(msg_id), &to_string_lossy(setup_code)) + .await + { + Ok(()) => 1, + Err(err) => { + error!(&ctx, "dc_continue_key_transfer: {}", err); + 0 } - }) - .unwrap_or(0) + } + }) + .unwrap_or(0) } #[no_mangle] @@ -1912,7 +1856,7 @@ pub unsafe extern "C" fn dc_stop_ongoing_process(context: *mut dc_context_t) { return; } let ffi_context = &*context; - ffi_context.with_inner(|ctx| ctx.stop_ongoing()).ok(); + with_inner_async!(ffi_context, ctx, ctx.stop_ongoing()).ok(); } #[no_mangle] @@ -1925,12 +1869,12 @@ pub unsafe extern "C" fn dc_check_qr( return ptr::null_mut(); } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| { - let lot = qr::check_qr(ctx, to_string_lossy(qr)); - Box::into_raw(Box::new(lot)) - }) - .unwrap_or_else(|_| ptr::null_mut()) + + with_inner_async!(ffi_context, ctx, async move { + let lot = qr::check_qr(&ctx, to_string_lossy(qr)).await; + Box::into_raw(Box::new(lot)) + }) + .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -1943,13 +1887,14 @@ pub unsafe extern "C" fn dc_get_securejoin_qr( return "".strdup(); } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| { - securejoin::dc_get_securejoin_qr(ctx, ChatId::new(chat_id)) - .unwrap_or_else(|| "".to_string()) - .strdup() - }) - .unwrap_or_else(|_| "".strdup()) + + with_inner_async!(ffi_context, ctx, async move { + securejoin::dc_get_securejoin_qr(&ctx, ChatId::new(chat_id)) + .await + .unwrap_or_else(|| "".to_string()) + .strdup() + }) + .unwrap_or_else(|_| "".strdup()) } #[no_mangle] @@ -1962,9 +1907,12 @@ pub unsafe extern "C" fn dc_join_securejoin( return 0; } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| securejoin::dc_join_securejoin(ctx, &to_string_lossy(qr)).to_u32()) - .unwrap_or(0) + + with_inner_async!(ffi_context, ctx, async move { + securejoin::dc_join_securejoin(&ctx, &to_string_lossy(qr)).await + }) + .map(|v| v.to_u32()) + .unwrap_or(0) } #[no_mangle] @@ -1978,11 +1926,11 @@ pub unsafe extern "C" fn dc_send_locations_to_chat( return; } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| { - location::send_locations_to_chat(ctx, ChatId::new(chat_id), seconds as i64) - }) - .ok(); + + with_inner_async!(ffi_context, ctx, { + location::send_locations_to_chat(&ctx, ChatId::new(chat_id), seconds as i64) + }) + .ok(); } #[no_mangle] @@ -1995,11 +1943,13 @@ pub unsafe extern "C" fn dc_is_sending_locations_to_chat( return 0; } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| { - location::is_sending_locations_to_chat(ctx, ChatId::new(chat_id)) as libc::c_int - }) - .unwrap_or(0) + + with_inner_async!( + ffi_context, + ctx, + location::is_sending_locations_to_chat(&ctx, ChatId::new(chat_id)) + ) + .unwrap_or_default() as libc::c_int } #[no_mangle] @@ -2014,9 +1964,13 @@ pub unsafe extern "C" fn dc_set_location( return 0; } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| location::set(ctx, latitude, longitude, accuracy)) - .unwrap_or(false) as _ + + with_inner_async!( + ffi_context, + ctx, + location::set(&ctx, latitude, longitude, accuracy) + ) + .unwrap_or(false) as _ } #[no_mangle] @@ -2032,18 +1986,19 @@ pub unsafe extern "C" fn dc_get_locations( return ptr::null_mut(); } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| { - let res = location::get_range( - ctx, - ChatId::new(chat_id), - contact_id, - timestamp_begin as i64, - timestamp_end as i64, - ); - Box::into_raw(Box::new(dc_array_t::from(res))) - }) - .unwrap_or_else(|_| ptr::null_mut()) + + with_inner_async!(ffi_context, ctx, async move { + let res = location::get_range( + &ctx, + ChatId::new(chat_id), + contact_id, + timestamp_begin as i64, + timestamp_end as i64, + ) + .await; + Box::into_raw(Box::new(dc_array_t::from(res))) + }) + .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -2053,11 +2008,13 @@ pub unsafe extern "C" fn dc_delete_all_locations(context: *mut dc_context_t) { return; } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| { - location::delete_all(ctx).log_err(ffi_context, "Failed to delete locations") - }) - .ok(); + + with_inner_async!(ffi_context, ctx, async move { + location::delete_all(&ctx) + .await + .log_err(ffi_context, "Failed to delete locations") + }) + .ok(); } // dc_array_t @@ -2335,12 +2292,15 @@ pub unsafe extern "C" fn dc_chatlist_get_summary( }; let ffi_list = &*chatlist; let ffi_context: &ContextWrapper = &*ffi_list.context; - ffi_context - .with_inner(|ctx| { - let lot = ffi_list.list.get_summary(ctx, index as usize, maybe_chat); - Box::into_raw(Box::new(lot)) - }) - .unwrap_or_else(|_| ptr::null_mut()) + + with_inner_async!(ffi_context, ctx, async move { + let lot = ffi_list + .list + .get_summary(&ctx, index as usize, maybe_chat) + .await; + Box::into_raw(Box::new(lot)) + }) + .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -2420,8 +2380,9 @@ pub unsafe extern "C" fn dc_chat_get_subtitle(chat: *mut dc_chat_t) -> *mut libc } let ffi_chat = &*chat; let ffi_context: &ContextWrapper = &*ffi_chat.context; - ffi_context - .with_inner(|ctx| ffi_chat.chat.get_subtitle(ctx).strdup()) + + with_inner_async!(ffi_context, ctx, ffi_chat.chat.get_subtitle(&ctx)) + .map(|s| s.strdup()) .unwrap_or_else(|_| "".strdup()) } @@ -2433,12 +2394,14 @@ pub unsafe extern "C" fn dc_chat_get_profile_image(chat: *mut dc_chat_t) -> *mut } let ffi_chat = &*chat; let ffi_context = &*ffi_chat.context; - ffi_context - .with_inner(|ctx| match ffi_chat.chat.get_profile_image(ctx) { + + with_inner_async!(ffi_context, ctx, async move { + match ffi_chat.chat.get_profile_image(&ctx).await { Some(p) => p.to_string_lossy().strdup(), None => ptr::null_mut(), - }) - .unwrap_or_else(|_| ptr::null_mut()) + } + }) + .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -2449,9 +2412,8 @@ pub unsafe extern "C" fn dc_chat_get_color(chat: *mut dc_chat_t) -> u32 { } let ffi_chat = &*chat; let ffi_context = &*ffi_chat.context; - ffi_context - .with_inner(|ctx| ffi_chat.chat.get_color(ctx)) - .unwrap_or(0) + + with_inner_async!(ffi_context, ctx, ffi_chat.chat.get_color(&ctx)).unwrap_or(0) } #[no_mangle] @@ -2569,30 +2531,30 @@ pub unsafe extern "C" fn dc_chat_get_info_json( return "".strdup(); } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| { - let chat = match chat::Chat::load_from_db(ctx, ChatId::new(chat_id)) { - Ok(chat) => chat, - Err(err) => { - error!(ctx, "dc_get_chat_info_json() failed to load chat: {}", err); - return "".strdup(); - } - }; - let info = match chat.get_info(ctx) { - Ok(info) => info, - Err(err) => { - error!( - ctx, - "dc_get_chat_info_json() failed to get chat info: {}", err - ); - return "".strdup(); - } - }; - serde_json::to_string(&info) - .unwrap_or_log_default(ctx, "dc_get_chat_info_json() failed to serialise to json") - .strdup() - }) - .unwrap_or_else(|_| "".strdup()) + + with_inner_async!(ffi_context, ctx, async move { + let chat = match chat::Chat::load_from_db(&ctx, ChatId::new(chat_id)).await { + Ok(chat) => chat, + Err(err) => { + error!(&ctx, "dc_get_chat_info_json() failed to load chat: {}", err); + return "".strdup(); + } + }; + let info = match chat.get_info(&ctx).await { + Ok(info) => info, + Err(err) => { + error!( + &ctx, + "dc_get_chat_info_json() failed to get chat info: {}", err + ); + return "".strdup(); + } + }; + serde_json::to_string(&info) + .unwrap_or_log_default(&ctx, "dc_get_chat_info_json() failed to serialise to json") + .strdup() + }) + .unwrap_or_else(|_| "".strdup()) } // dc_msg_t @@ -2786,9 +2748,8 @@ pub unsafe extern "C" fn dc_msg_get_filebytes(msg: *mut dc_msg_t) -> u64 { } let ffi_msg = &*msg; let ffi_context = &*ffi_msg.context; - ffi_context - .with_inner(|ctx| ffi_msg.message.get_filebytes(ctx)) - .unwrap_or(0) + + with_inner_async!(ffi_context, ctx, ffi_msg.message.get_filebytes(&ctx)).unwrap_or(0) } #[no_mangle] @@ -2848,12 +2809,12 @@ pub unsafe extern "C" fn dc_msg_get_summary( }; let ffi_msg = &mut *msg; let ffi_context = &*ffi_msg.context; - ffi_context - .with_inner(|ctx| { - let lot = ffi_msg.message.get_summary(ctx, maybe_chat); - Box::into_raw(Box::new(lot)) - }) - .unwrap_or_else(|_| ptr::null_mut()) + + with_inner_async!(ffi_context, ctx, async move { + let lot = ffi_msg.message.get_summary(&ctx, maybe_chat).await; + Box::into_raw(Box::new(lot)) + }) + .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -2867,14 +2828,14 @@ pub unsafe extern "C" fn dc_msg_get_summarytext( } let ffi_msg = &mut *msg; let ffi_context = &*ffi_msg.context; - ffi_context - .with_inner(|ctx| { - ffi_msg - .message - .get_summarytext(ctx, approx_characters.try_into().unwrap_or_default()) - }) - .unwrap_or_default() - .strdup() + + with_inner_async!(ffi_context, ctx, { + ffi_msg + .message + .get_summarytext(&ctx, approx_characters.try_into().unwrap_or_default()) + }) + .unwrap_or_default() + .strdup() } #[no_mangle] @@ -2965,8 +2926,9 @@ pub unsafe extern "C" fn dc_msg_get_setupcodebegin(msg: *mut dc_msg_t) -> *mut l } let ffi_msg = &*msg; let ffi_context = &*ffi_msg.context; - ffi_context - .with_inner(|ctx| ffi_msg.message.get_setupcodebegin(ctx).unwrap_or_default()) + + with_inner_async!(ffi_context, ctx, ffi_msg.message.get_setupcodebegin(&ctx)) + .map(|s| s.unwrap_or_default()) .unwrap_or_default() .strdup() } @@ -3049,13 +3011,13 @@ pub unsafe extern "C" fn dc_msg_latefiling_mediasize( } let ffi_msg = &mut *msg; let ffi_context = &*ffi_msg.context; - ffi_context - .with_inner(|ctx| { - ffi_msg - .message - .latefiling_mediasize(ctx, width, height, duration) - }) - .ok(); + + with_inner_async!(ffi_context, ctx, { + ffi_msg + .message + .latefiling_mediasize(&ctx, width, height, duration) + }) + .ok(); } // dc_contact_t @@ -3160,15 +3122,16 @@ pub unsafe extern "C" fn dc_contact_get_profile_image( } let ffi_contact = &*contact; let ffi_context = &*ffi_contact.context; - ffi_context - .with_inner(|ctx| { - ffi_contact - .contact - .get_profile_image(ctx) - .map(|p| p.to_string_lossy().strdup()) - .unwrap_or_else(std::ptr::null_mut) - }) - .unwrap_or_else(|_| ptr::null_mut()) + + with_inner_async!(ffi_context, ctx, async move { + ffi_contact + .contact + .get_profile_image(&ctx) + .await + .map(|p| p.to_string_lossy().strdup()) + .unwrap_or_else(std::ptr::null_mut) + }) + .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -3199,9 +3162,11 @@ pub unsafe extern "C" fn dc_contact_is_verified(contact: *mut dc_contact_t) -> l } let ffi_contact = &*contact; let ffi_context = &*ffi_contact.context; - ffi_context - .with_inner(|ctx| ffi_contact.contact.is_verified(ctx) as libc::c_int) - .unwrap_or(0) + + with_inner_async!(ffi_context, ctx, async move { + ffi_contact.contact.is_verified(&ctx).await as libc::c_int + }) + .unwrap_or(0) } // dc_lot_t From 11fa60d690dbf8e6706f37641f36a093c2535d60 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Sat, 21 Mar 2020 23:37:52 +0100 Subject: [PATCH 026/118] stop python tests from blowing up --- python/fail_test.py | 7 ++++ python/src/deltachat/account.py | 72 +++++---------------------------- python/tests/test_account.py | 2 + python/tests/test_lowlevel.py | 62 ++++++++++++++++++++++------ 4 files changed, 68 insertions(+), 75 deletions(-) create mode 100644 python/fail_test.py diff --git a/python/fail_test.py b/python/fail_test.py new file mode 100644 index 000000000..5d1535fdf --- /dev/null +++ b/python/fail_test.py @@ -0,0 +1,7 @@ +from __future__ import print_function +from deltachat import capi +from deltachat.capi import ffi, lib + +if __name__ == "__main__": + ctx = capi.lib.dc_context_new(ffi.NULL, ffi.NULL) + lib.dc_context_shutdown(ctx) diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index d699aff6d..4e1e28447 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -35,7 +35,7 @@ class Account(object): :param debug: turn on debug logging for events. """ self._dc_context = ffi.gc( - lib.dc_context_new(lib.py_dc_callback, ffi.NULL, as_dc_charpointer(os_name)), + lib.dc_context_new(ffi.NULL, as_dc_charpointer(os_name)), _destroy_dc_context, ) self._evlogger = EventLogger(self, logid, debug) @@ -384,8 +384,6 @@ class Account(object): def _export(self, path, imex_cmd): with ImexTracker(self) as imex_tracker: lib.dc_imex(self._dc_context, imex_cmd, as_dc_charpointer(path), ffi.NULL) - if not self._threads.is_started(): - lib.dc_perform_imap_jobs(self._dc_context) return imex_tracker.wait_finish() def import_self_keys(self, path): @@ -406,8 +404,6 @@ class Account(object): def _import(self, path, imex_cmd): with ImexTracker(self) as imex_tracker: lib.dc_imex(self._dc_context, imex_cmd, as_dc_charpointer(path), ffi.NULL) - if not self._threads.is_started(): - lib.dc_perform_imap_jobs(self._dc_context) imex_tracker.wait_finish() def initiate_key_transfer(self): @@ -485,7 +481,7 @@ class Account(object): ev = self._evlogger.get_matching("DC_EVENT_INCOMING_MSG") return self.get_message_by_id(ev[2]) - def start_threads(self, mvbox=False, sentbox=False): + def start_threads(self): """ start IMAP/SMTP threads (and configure account if it hasn't happened). :raises: ValueError if 'addr' or 'mail_pw' are not configured. @@ -493,7 +489,7 @@ class Account(object): """ if not self.is_configured(): self.configure() - self._threads.start(mvbox=mvbox, sentbox=sentbox) + self._threads.start() def stop_threads(self, wait=True): """ stop IMAP/SMTP threads. """ @@ -575,14 +571,7 @@ class IOThreads: def start(self, imap=True, smtp=True, mvbox=False, sentbox=False): assert not self.is_started() - if imap: - self._start_one_thread("inbox", self.imap_thread_run) - if mvbox: - self._start_one_thread("mvbox", self.mvbox_thread_run) - if sentbox: - self._start_one_thread("sentbox", self.sentbox_thread_run) - if smtp: - self._start_one_thread("smtp", self.smtp_thread_run) + self._start_one_thread("deltachat", self.dc_thread_run) def _start_one_thread(self, name, func): self._name2thread[name] = t = threading.Thread(target=func, name=name) @@ -590,58 +579,17 @@ class IOThreads: t.start() def stop(self, wait=False): - self._thread_quitflag = True - - # Workaround for a race condition. Make sure that thread is - # not in between checking for quitflag and entering idle. - time.sleep(0.5) - - lib.dc_interrupt_imap_idle(self._dc_context) - lib.dc_interrupt_smtp_idle(self._dc_context) - lib.dc_interrupt_mvbox_idle(self._dc_context) - lib.dc_interrupt_sentbox_idle(self._dc_context) + lib.dc_context_shutdown(self._dc_context) if wait: for name, thread in self._name2thread.items(): thread.join() - def imap_thread_run(self): - self._log_event("py-bindings-info", 0, "INBOX THREAD START") - while not self._thread_quitflag: - lib.dc_perform_imap_jobs(self._dc_context) - if not self._thread_quitflag: - lib.dc_perform_imap_fetch(self._dc_context) - if not self._thread_quitflag: - lib.dc_perform_imap_idle(self._dc_context) - self._log_event("py-bindings-info", 0, "INBOX THREAD FINISHED") - - def mvbox_thread_run(self): - self._log_event("py-bindings-info", 0, "MVBOX THREAD START") - while not self._thread_quitflag: - lib.dc_perform_mvbox_jobs(self._dc_context) - if not self._thread_quitflag: - lib.dc_perform_mvbox_fetch(self._dc_context) - if not self._thread_quitflag: - lib.dc_perform_mvbox_idle(self._dc_context) - self._log_event("py-bindings-info", 0, "MVBOX THREAD FINISHED") - - def sentbox_thread_run(self): - self._log_event("py-bindings-info", 0, "SENTBOX THREAD START") - while not self._thread_quitflag: - lib.dc_perform_sentbox_jobs(self._dc_context) - if not self._thread_quitflag: - lib.dc_perform_sentbox_fetch(self._dc_context) - if not self._thread_quitflag: - lib.dc_perform_sentbox_idle(self._dc_context) - self._log_event("py-bindings-info", 0, "SENTBOX THREAD FINISHED") - - def smtp_thread_run(self): - self._log_event("py-bindings-info", 0, "SMTP THREAD START") - while not self._thread_quitflag: - lib.dc_perform_smtp_jobs(self._dc_context) - if not self._thread_quitflag: - lib.dc_perform_smtp_idle(self._dc_context) - self._log_event("py-bindings-info", 0, "SMTP THREAD FINISHED") + def dc_thread_run(self): + self._log_event("py-bindings-info", 0, "DC THREAD START") + + lib.dc_context_run(self._dc_context, lib.py_dc_callback) + self._log_event("py-bindings-info", 0, "DC THREAD FINISHED") def _destroy_dc_context(dc_context, dc_context_unref=lib.dc_context_unref): # destructor for dc_context diff --git a/python/tests/test_account.py b/python/tests/test_account.py index 71c8322d3..1b868e191 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -178,6 +178,7 @@ class TestOfflineChat: assert d["draft"] == "" if chat.get_draft() is None else chat.get_draft() def test_group_chat_creation_with_translation(self, ac1): + ac1.start_threads() ac1.set_stock_translation(const.DC_STR_NEWGROUPDRAFT, "xyz %1$s") ac1._evlogger.consume_events() with pytest.raises(ValueError): @@ -197,6 +198,7 @@ class TestOfflineChat: assert not chat.is_promoted() msg = chat.get_draft() assert msg.text == "xyz title1" + ac1.stop_threads() @pytest.mark.parametrize("verified", [True, False]) def test_group_chat_qr(self, acfactory, ac1, verified): diff --git a/python/tests/test_lowlevel.py b/python/tests/test_lowlevel.py index bf3b1f085..3f64946c7 100644 --- a/python/tests/test_lowlevel.py +++ b/python/tests/test_lowlevel.py @@ -1,29 +1,64 @@ from __future__ import print_function +import threading from deltachat import capi, cutil, const, set_context_callback, clear_context_callback from deltachat.capi import ffi from deltachat.capi import lib +from deltachat.account import EventLogger + + +class EventThread(threading.Thread): + def __init__(self, dc_context): + self.dc_context = dc_context + super(EventThread, self).__init__() + self.setDaemon(1) + + def run(self): + lib.dc_context_run(self.dc_context)#, lib.py_dc_callback) + + def stop(self): + lib.dc_context_shutdown(self.dc_context) def test_empty_context(): - ctx = capi.lib.dc_context_new(capi.ffi.NULL, capi.ffi.NULL, capi.ffi.NULL) + ctx = capi.lib.dc_context_new(capi.ffi.NULL, capi.ffi.NULL) capi.lib.dc_close(ctx) def test_callback_None2int(): - ctx = capi.lib.dc_context_new(capi.lib.py_dc_callback, ffi.NULL, ffi.NULL) + ctx = capi.lib.dc_context_new(ffi.NULL, ffi.NULL) set_context_callback(ctx, lambda *args: None) capi.lib.dc_close(ctx) clear_context_callback(ctx) +def test_start_stop_event_thread_basic(): + print("1") + ctx = capi.lib.dc_context_new(ffi.NULL, ffi.NULL) + print("2") + ev_thread = EventThread(ctx) + print("3 -- starting event thread") + ev_thread.start() + print("4 -- stopping event thread") + ev_thread.stop() def test_dc_close_events(tmpdir): - from deltachat.account import Account + ctx = ffi.gc( + capi.lib.dc_context_new(ffi.NULL, ffi.NULL), + lib.dc_context_unref, + ) + evlog = EventLogger(ctx) + evlog.set_timeout(5) + set_context_callback( + ctx, + lambda ctx, evt_name, data1, data2: evlog(evt_name, data1, data2) + ) + ev_thread = EventThread(ctx) + ev_thread.start() + p = tmpdir.join("hello.db") - ac1 = Account(p.strpath) - ac1.shutdown() + lib.dc_open(ctx, p.strpath.encode("ascii"), ffi.NULL) + capi.lib.dc_close(ctx) def find(info_string): - evlog = ac1._evlogger while 1: ev = evlog.get_matching("DC_EVENT_INFO", check_error=False) data2 = ev[2] @@ -37,11 +72,12 @@ def test_dc_close_events(tmpdir): find("disconnecting mvbox-thread") find("disconnecting SMTP") find("Database closed") + ev_thread.stop() def test_wrong_db(tmpdir): dc_context = ffi.gc( - lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL), + lib.dc_context_new(ffi.NULL, ffi.NULL), lib.dc_context_unref, ) p = tmpdir.join("hello.db") @@ -53,7 +89,7 @@ def test_wrong_db(tmpdir): def test_empty_blobdir(tmpdir): # Apparently some client code expects this to be the same as passing NULL. ctx = ffi.gc( - lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL), + lib.dc_context_new(ffi.NULL, ffi.NULL), lib.dc_context_unref, ) db_fname = tmpdir.join("hello.db") @@ -95,7 +131,7 @@ def test_get_special_message_id_returns_empty_message(acfactory): def test_provider_info_none(): ctx = ffi.gc( - lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL), + lib.dc_context_new(ffi.NULL, ffi.NULL), lib.dc_context_unref, ) assert lib.dc_provider_new_from_email(ctx, cutil.as_dc_charpointer("email@unexistent.no")) == ffi.NULL @@ -103,7 +139,7 @@ def test_provider_info_none(): def test_get_info_closed(): ctx = ffi.gc( - lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL), + lib.dc_context_new(ffi.NULL, ffi.NULL), lib.dc_context_unref, ) info = cutil.from_dc_charpointer(lib.dc_get_info(ctx)) @@ -113,7 +149,7 @@ def test_get_info_closed(): def test_get_info_open(tmpdir): ctx = ffi.gc( - lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL), + lib.dc_context_new(ffi.NULL, ffi.NULL), lib.dc_context_unref, ) db_fname = tmpdir.join("test.db") @@ -125,7 +161,7 @@ def test_get_info_open(tmpdir): def test_is_open_closed(): ctx = ffi.gc( - lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL), + lib.dc_context_new(ffi.NULL, ffi.NULL), lib.dc_context_unref, ) assert lib.dc_is_open(ctx) == 0 @@ -133,7 +169,7 @@ def test_is_open_closed(): def test_is_open_actually_open(tmpdir): ctx = ffi.gc( - lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL), + lib.dc_context_new(ffi.NULL, ffi.NULL), lib.dc_context_unref, ) db_fname = tmpdir.join("test.db") From 9b4c195872232446a360c42f29775d07b267f960 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Sun, 22 Mar 2020 01:07:06 +0100 Subject: [PATCH 027/118] minimal get_next_event api --- deltachat-ffi/deltachat.h | 35 +++-- deltachat-ffi/src/lib.rs | 234 +++++++++++++++++++++---------- python/src/deltachat/__init__.py | 16 +-- python/src/deltachat/_build.py | 7 - python/src/deltachat/account.py | 22 ++- python/tests/test_lowlevel.py | 83 +++++++---- 6 files changed, 254 insertions(+), 143 deletions(-) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 71350aec8..63b9e1060 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -19,6 +19,7 @@ typedef struct _dc_msg dc_msg_t; typedef struct _dc_contact dc_contact_t; typedef struct _dc_lot dc_lot_t; typedef struct _dc_provider dc_provider_t; +typedef struct _dc_event dc_event_t; /** @@ -189,6 +190,26 @@ typedef struct _dc_provider dc_provider_t; */ + +/** + * TODO: document + */ +int dc_has_next_event(dc_context_t* context); + +/** + * TODO: document + */ +dc_event_t* dc_get_next_event(dc_context_t* context); + +int dc_event_get_id (dc_event_t* event); +uintptr_t dc_event_get_data1(dc_event_t* event); +uintptr_t dc_event_get_data2(dc_event_t* event); + +/** + * TODO: document + */ +void dc_event_unref (dc_event_t* event); + /** * @class dc_context_t * @@ -199,20 +220,6 @@ typedef struct _dc_provider dc_provider_t; * settings. */ - -/** - * Callback function that should be given to dc_context_new(). - * - * @memberof dc_context_t - * @param context The context object as returned by dc_context_new(). - * @param event one of the @ref DC_EVENT constants - * @param data1 depends on the event parameter - * @param data2 depends on the event parameter - * @return return 0 unless stated otherwise in the event parameter documentation - */ -typedef uintptr_t (*dc_callback_t) (dc_context_t* context, int event, uintptr_t data1, uintptr_t data2); - - // create/open/config/information /** diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 5d95fcaa9..8ae9d34e8 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -464,10 +464,7 @@ pub unsafe extern "C" fn dc_is_configured(context: *mut dc_context_t) -> libc::c #[no_mangle] pub unsafe extern "C" fn dc_context_run(context: *mut dc_context_t) { - eprintln!("dc_context_run"); - if context.is_null() { - eprintln!("ignoring careless call to dc_run()"); return; } let ffi_context = &*context; @@ -475,81 +472,168 @@ pub unsafe extern "C" fn dc_context_run(context: *mut dc_context_t) { with_inner_async!(ffi_context, ctx, { ctx.run() }).unwrap_or(()) } +#[no_mangle] +pub unsafe extern "C" fn dc_has_next_event(context: *mut dc_context_t) -> libc::c_int { + if context.is_null() { + return 0; + } + let ffi_context = &*context; + + ffi_context + .with_inner(|ctx| ctx.has_next_event()) + .unwrap_or_default() as libc::c_int +} + +pub struct EventWrapper { + pub event_id: libc::c_int, + pub data1: uintptr_t, + pub data2: uintptr_t, +} + +#[no_mangle] +pub type dc_event_t = EventWrapper; + +#[no_mangle] +pub unsafe extern "C" fn dc_event_unref(a: *mut dc_event_t) { + if a.is_null() { + eprintln!("ignoring careless call to dc_event_unref()"); + return; + } + + Box::from_raw(a); +} + +#[no_mangle] +pub unsafe extern "C" fn dc_event_get_id(event: *mut dc_event_t) -> libc::c_int { + if event.is_null() { + eprintln!("ignoring careless call to dc_event_get_id()"); + return 0; + } + + let event = &*event; + event.event_id +} + +#[no_mangle] +pub unsafe extern "C" fn dc_event_get_data1(event: *mut dc_event_t) -> uintptr_t { + if event.is_null() { + eprintln!("ignoring careless call to dc_event_get_data1()"); + return 0; + } + + let event = &*event; + event.data1 +} + +#[no_mangle] +pub unsafe extern "C" fn dc_event_get_data2(event: *mut dc_event_t) -> uintptr_t { + if event.is_null() { + eprintln!("ignoring careless call to dc_event_get_data2()"); + return 0; + } + + let event = &*event; + event.data2 +} + +#[no_mangle] +pub unsafe extern "C" fn dc_get_next_event(context: *mut dc_context_t) -> *mut dc_event_t { + if context.is_null() { + return ptr::null_mut(); + } + let ffi_context = &*context; + + ffi_context + .with_inner(|ctx| match ctx.get_next_event() { + Ok(ev) => translate_event(ev), + Err(_) => ptr::null_mut(), + }) + .unwrap_or_else(|_| ptr::null_mut()) +} + /// Translates the callback from the rust style to the C-style version. -unsafe fn translate_cb(ctx: &ContextWrapper, ffi_cb: Option, event: Event) { - if let Some(ffi_cb) = ffi_cb { - let event_id = event.as_id(); - match event { - Event::Info(msg) - | Event::SmtpConnected(msg) - | Event::ImapConnected(msg) - | Event::SmtpMessageSent(msg) - | Event::ImapMessageDeleted(msg) - | Event::ImapMessageMoved(msg) - | Event::ImapFolderEmptied(msg) - | Event::NewBlobFile(msg) - | Event::DeletedBlobFile(msg) - | Event::Warning(msg) - | Event::Error(msg) - | Event::ErrorNetwork(msg) - | Event::ErrorSelfNotInGroup(msg) => { - let data2 = CString::new(msg).unwrap_or_default(); - ffi_cb(ctx, event_id, 0, data2.as_ptr() as uintptr_t); - } - Event::MsgsChanged { chat_id, msg_id } - | Event::IncomingMsg { chat_id, msg_id } - | Event::MsgDelivered { chat_id, msg_id } - | Event::MsgFailed { chat_id, msg_id } - | Event::MsgRead { chat_id, msg_id } => { - ffi_cb( - ctx, - event_id, - chat_id.to_u32() as uintptr_t, - msg_id.to_u32() as uintptr_t, - ); - } - Event::ChatModified(chat_id) => { - ffi_cb(ctx, event_id, chat_id.to_u32() as uintptr_t, 0); - } - Event::ContactsChanged(id) | Event::LocationChanged(id) => { - let id = id.unwrap_or_default(); - ffi_cb(ctx, event_id, id as uintptr_t, 0); - } - Event::ConfigureProgress(progress) | Event::ImexProgress(progress) => { - ffi_cb(ctx, event_id, progress as uintptr_t, 0); - } - Event::ImexFileWritten(file) => { - let data1 = file.to_c_string().unwrap_or_default(); - ffi_cb(ctx, event_id, data1.as_ptr() as uintptr_t, 0); - } - Event::SecurejoinInviterProgress { - contact_id, - progress, - } - | Event::SecurejoinJoinerProgress { - contact_id, - progress, - } => { - ffi_cb( - ctx, - event_id, - contact_id as uintptr_t, - progress as uintptr_t, - ); - } - Event::SecurejoinMemberAdded { - chat_id, - contact_id, - } => { - ffi_cb( - ctx, - event_id, - chat_id.to_u32() as uintptr_t, - contact_id as uintptr_t, - ); +unsafe fn translate_event(event: Event) -> *mut dc_event_t { + let event_id = event.as_id(); + let wrapper = match event { + Event::Info(msg) + | Event::SmtpConnected(msg) + | Event::ImapConnected(msg) + | Event::SmtpMessageSent(msg) + | Event::ImapMessageDeleted(msg) + | Event::ImapMessageMoved(msg) + | Event::ImapFolderEmptied(msg) + | Event::NewBlobFile(msg) + | Event::DeletedBlobFile(msg) + | Event::Warning(msg) + | Event::Error(msg) + | Event::ErrorNetwork(msg) + | Event::ErrorSelfNotInGroup(msg) => { + let data2 = CString::new(msg).unwrap_or_default(); + + EventWrapper { + event_id, + data1: 0, + data2: data2.into_raw() as uintptr_t, } } - } + Event::MsgsChanged { chat_id, msg_id } + | Event::IncomingMsg { chat_id, msg_id } + | Event::MsgDelivered { chat_id, msg_id } + | Event::MsgFailed { chat_id, msg_id } + | Event::MsgRead { chat_id, msg_id } => EventWrapper { + event_id, + data1: chat_id.to_u32() as uintptr_t, + data2: msg_id.to_u32() as uintptr_t, + }, + Event::ChatModified(chat_id) => EventWrapper { + event_id, + data1: chat_id.to_u32() as uintptr_t, + data2: 0, + }, + Event::ContactsChanged(id) | Event::LocationChanged(id) => { + let id = id.unwrap_or_default(); + EventWrapper { + event_id, + data1: id as uintptr_t, + data2: 0, + } + } + Event::ConfigureProgress(progress) | Event::ImexProgress(progress) => EventWrapper { + event_id, + data1: progress as uintptr_t, + data2: 0, + }, + Event::ImexFileWritten(file) => { + let data1 = file.to_c_string().unwrap_or_default(); + EventWrapper { + event_id, + data1: data1.into_raw() as uintptr_t, + data2: 0, + } + } + Event::SecurejoinInviterProgress { + contact_id, + progress, + } + | Event::SecurejoinJoinerProgress { + contact_id, + progress, + } => EventWrapper { + event_id, + data1: contact_id as uintptr_t, + data2: progress as uintptr_t, + }, + Event::SecurejoinMemberAdded { + chat_id, + contact_id, + } => EventWrapper { + event_id, + data1: chat_id.to_u32() as uintptr_t, + data2: contact_id as uintptr_t, + }, + }; + + Box::into_raw(Box::new(wrapper)) } #[no_mangle] diff --git a/python/src/deltachat/__init__.py b/python/src/deltachat/__init__.py index 4a8f21bf9..d18003480 100644 --- a/python/src/deltachat/__init__.py +++ b/python/src/deltachat/__init__.py @@ -13,7 +13,6 @@ except DistributionNotFound: _DC_CALLBACK_MAP = {} -@capi.ffi.def_extern() def py_dc_callback(ctx, evt, data1, data2): """The global event handler. @@ -32,9 +31,9 @@ def py_dc_callback(ctx, evt, data1, data2): evt_name = get_dc_event_name(evt) event_sig_types = capi.lib.dc_get_event_signature_types(evt) if data1 and event_sig_types & 1: - data1 = ffi.string(ffi.cast('char*', data1)).decode("utf8") + data1 = ffi.string(ffi.gc(ffi.cast('char*', data1), capi.lib.dc_str_unref)).decode("utf8") if data2 and event_sig_types & 2: - data2 = ffi.string(ffi.cast('char*', data2)).decode("utf8") + data2 = ffi.string(ffi.gc(ffi.cast('char*', data2), capi.lib.dc_str_unref)).decode("utf8") try: if isinstance(data2, bytes): data2 = data2.decode("utf8") @@ -43,18 +42,9 @@ def py_dc_callback(ctx, evt, data1, data2): # i don't want to hunt down encoding problems in the c lib pass try: - ret = callback(ctx, evt_name, data1, data2) - if ret is None: - ret = 0 - assert isinstance(ret, int), repr(ret) - if event_sig_types & 4: - return ffi.cast('uintptr_t', ret) - elif event_sig_types & 8: - return ffi.cast('int', ret) + callback(ctx, evt_name, data1, data2) except: # noqa raise - ret = 0 - return ret def set_context_callback(dc_context, func): diff --git a/python/src/deltachat/_build.py b/python/src/deltachat/_build.py index 7e0039fa4..e92d9e174 100644 --- a/python/src/deltachat/_build.py +++ b/python/src/deltachat/_build.py @@ -92,13 +92,6 @@ def ffibuilder(): finally: shutil.rmtree(tmpdir) - builder.cdef(""" - extern "Python" uintptr_t py_dc_callback( - dc_context_t* context, - int event, - uintptr_t data1, - uintptr_t data2); - """) return builder diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index 4e1e28447..313395fe9 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -565,12 +565,14 @@ class IOThreads: self._thread_quitflag = False self._name2thread = {} self._log_event = log_event + self._running = False def is_started(self): - return len(self._name2thread) > 0 + return self._running def start(self, imap=True, smtp=True, mvbox=False, sentbox=False): assert not self.is_started() + self._running = True self._start_one_thread("deltachat", self.dc_thread_run) def _start_one_thread(self, name, func): @@ -583,11 +585,25 @@ class IOThreads: if wait: for name, thread in self._name2thread.items(): thread.join() + self._running = False def dc_thread_run(self): self._log_event("py-bindings-info", 0, "DC THREAD START") - - lib.dc_context_run(self._dc_context, lib.py_dc_callback) + + lib.dc_context_run(self._dc_context) + while self._running: + if lib.dc_has_next_event(self._dc_context): + event = lib.dc_get_next_event(self._dc_context) + if event != ffi.NULL: + deltachat.py_dc_callback( + self._dc_context, + lib.dc_event_get_id(event), + lib.dc_event_get_data1(event), + lib.dc_event_get_data2(event) + ) + lib.dc_event_unref(event) + else: + time.sleep(0.05) self._log_event("py-bindings-info", 0, "DC THREAD FINISHED") diff --git a/python/tests/test_lowlevel.py b/python/tests/test_lowlevel.py index 3f64946c7..60640a228 100644 --- a/python/tests/test_lowlevel.py +++ b/python/tests/test_lowlevel.py @@ -1,5 +1,6 @@ from __future__ import print_function import threading +import time from deltachat import capi, cutil, const, set_context_callback, clear_context_callback from deltachat.capi import ffi from deltachat.capi import lib @@ -11,12 +12,27 @@ class EventThread(threading.Thread): self.dc_context = dc_context super(EventThread, self).__init__() self.setDaemon(1) + self._running = True def run(self): - lib.dc_context_run(self.dc_context)#, lib.py_dc_callback) + lib.dc_context_run(self.dc_context) + while self._running: + if lib.dc_has_next_event(self.dc_context): + event = lib.dc_get_next_event(self.dc_context) + if event != ffi.NULL: + deltachat.py_dc_callback( + self._dc_context, + lib.dc_event_get_id(event), + lib.dc_event_get_data1(event), + lib.dc_event_get_data2(event) + ) + lib.dc_event_unref(event) + else: + time.sleep(0.05) def stop(self): lib.dc_context_shutdown(self.dc_context) + self._running = False def test_empty_context(): @@ -40,39 +56,41 @@ def test_start_stop_event_thread_basic(): print("4 -- stopping event thread") ev_thread.stop() -def test_dc_close_events(tmpdir): - ctx = ffi.gc( - capi.lib.dc_context_new(ffi.NULL, ffi.NULL), - lib.dc_context_unref, - ) - evlog = EventLogger(ctx) - evlog.set_timeout(5) - set_context_callback( - ctx, - lambda ctx, evt_name, data1, data2: evlog(evt_name, data1, data2) - ) - ev_thread = EventThread(ctx) - ev_thread.start() - p = tmpdir.join("hello.db") - lib.dc_open(ctx, p.strpath.encode("ascii"), ffi.NULL) - capi.lib.dc_close(ctx) +# FIXME: EventLogger doesn't work without an account anymore +# def test_dc_close_events(tmpdir): +# ctx = ffi.gc( +# capi.lib.dc_context_new(ffi.NULL, ffi.NULL), +# lib.dc_context_unref, +# ) +# evlog = EventLogger(ctx) +# evlog.set_timeout(5) +# set_context_callback( +# ctx, +# lambda ctx, evt_name, data1, data2: evlog(evt_name, data1, data2) +# ) +# ev_thread = EventThread(ctx) +# ev_thread.start() - def find(info_string): - while 1: - ev = evlog.get_matching("DC_EVENT_INFO", check_error=False) - data2 = ev[2] - if info_string in data2: - return - else: - print("skipping event", *ev) +# p = tmpdir.join("hello.db") +# lib.dc_open(ctx, p.strpath.encode("ascii"), ffi.NULL) +# capi.lib.dc_close(ctx) - find("disconnecting inbox-thread") - find("disconnecting sentbox-thread") - find("disconnecting mvbox-thread") - find("disconnecting SMTP") - find("Database closed") - ev_thread.stop() +# def find(info_string): +# while 1: +# ev = evlog.get_matching("DC_EVENT_INFO", check_error=False) +# data2 = ev[2] +# if info_string in data2: +# return +# else: +# print("skipping event", *ev) + +# find("disconnecting inbox-thread") +# find("disconnecting sentbox-thread") +# find("disconnecting mvbox-thread") +# find("disconnecting SMTP") +# find("Database closed") +# ev_thread.stop() def test_wrong_db(tmpdir): @@ -113,6 +131,8 @@ def test_sig(): def test_markseen_invalid_message_ids(acfactory): ac1 = acfactory.get_configured_offline_account() + + ac1.start_threads() contact1 = ac1.create_contact(email="some1@example.com", name="some1") chat = ac1.create_chat_by_contact(contact1) chat.send_text("one messae") @@ -120,6 +140,7 @@ def test_markseen_invalid_message_ids(acfactory): msg_ids = [9] lib.dc_markseen_msgs(ac1._dc_context, msg_ids, len(msg_ids)) ac1._evlogger.ensure_event_not_queued("DC_EVENT_WARNING|DC_EVENT_ERROR") + ac1.stop_threads() def test_get_special_message_id_returns_empty_message(acfactory): From 20ef115eb2c37dcb718548818e6cd792c4c82cfa Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Sun, 22 Mar 2020 16:21:15 +0100 Subject: [PATCH 028/118] fix various integration tests with the python bindings --- deltachat-ffi/deltachat.h | 2 ++ deltachat-ffi/src/lib.rs | 60 ++++++++++++++++++++++++++------- examples/simple.rs | 2 +- python/src/deltachat/account.py | 27 +++++++-------- python/tests/conftest.py | 2 +- python/tests/test_account.py | 10 +++--- python/tests/test_lowlevel.py | 4 +-- src/scheduler.rs | 18 +++++++--- 8 files changed, 86 insertions(+), 39 deletions(-) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 63b9e1060..4493bb633 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -572,6 +572,8 @@ int dc_is_configured (const dc_context_t* context); */ void dc_context_run (dc_context_t* context); +int dc_is_running (const dc_context_t* context); + /** * TODO: Document */ diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 8ae9d34e8..73c08dcc5 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -119,7 +119,7 @@ impl ContextWrapper { match guard.as_ref() { Some(ref ctx) => Ok(ctxfn(ctx)), None => { - eprintln!("context not open"); + eprintln!("ignoring careless call to non open context"); Err(()) } } @@ -142,7 +142,7 @@ macro_rules! with_inner_async { Ok(res) } None => { - eprintln!("context not open"); + eprintln!("ignoring careless call to non open context"); Err(()) } } @@ -150,6 +150,23 @@ macro_rules! with_inner_async { }}; } +macro_rules! with_inner_spawn { + ($ctx:expr, $name:ident, $block:expr) => {{ + let l = $ctx.inner.clone(); + let lock = l.read().unwrap(); + match lock.as_ref() { + Some(ctx) => { + let $name = ctx.clone(); + async_std::task::spawn(async move { $block.await }); + Ok(()) + } + None => { + eprintln!("ignoring careless call to non open context"); + Err(()) + } + } + }}; +} macro_rules! try_inner_async { ($ctx:expr, $name:ident, $block:expr) => {{ let l = $ctx.inner.clone(); @@ -443,9 +460,15 @@ pub unsafe extern "C" fn dc_configure(context: *mut dc_context_t) { eprintln!("ignoring careless call to dc_configure()"); return; } - // TODO: this is now blocking, maybe change this + let ffi_context = &*context; - with_inner_async!(ffi_context, ctx, async move { ctx.configure().await }).ok(); + + with_inner_spawn!(ffi_context, ctx, async move { + ctx.configure() + .await + .log_err(ffi_context, "Configure failed") + }) + .ok(); } #[no_mangle] @@ -472,6 +495,16 @@ pub unsafe extern "C" fn dc_context_run(context: *mut dc_context_t) { with_inner_async!(ffi_context, ctx, { ctx.run() }).unwrap_or(()) } +#[no_mangle] +pub unsafe extern "C" fn dc_is_running(context: *mut dc_context_t) -> libc::c_int { + if context.is_null() { + return 0; + } + let ffi_context = &*context; + + with_inner_async!(ffi_context, ctx, { ctx.is_running() }).unwrap_or_default() as libc::c_int +} + #[no_mangle] pub unsafe extern "C" fn dc_has_next_event(context: *mut dc_context_t) -> libc::c_int { if context.is_null() { @@ -1833,7 +1866,7 @@ pub unsafe extern "C" fn dc_get_contact( #[no_mangle] pub unsafe extern "C" fn dc_imex( context: *mut dc_context_t, - what: libc::c_int, + what_raw: libc::c_int, param1: *const libc::c_char, _param2: *const libc::c_char, ) { @@ -1841,21 +1874,24 @@ pub unsafe extern "C" fn dc_imex( eprintln!("ignoring careless call to dc_imex()"); return; } - let what = match imex::ImexMode::from_i32(what as i32) { + let what = match imex::ImexMode::from_i32(what_raw as i32) { Some(what) => what, None => { - eprintln!("ignoring invalid argument {} to dc_imex", what); + eprintln!("ignoring invalid argument {} to dc_imex", what_raw); return; } }; // TODO: this is now blocking, figure out if that is okay let ffi_context = &*context; - with_inner_async!( - ffi_context, - ctx, - imex::imex(&ctx, what, to_opt_string_lossy(param1)) - ) + + let param1 = to_opt_string_lossy(param1); + + with_inner_spawn!(ffi_context, ctx, async move { + imex::imex(&ctx, what, param1) + .await + .log_err(ffi_context, "IMEX failed") + }) .ok(); } diff --git a/examples/simple.rs b/examples/simple.rs index a94eab63e..354a9b7d3 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -46,7 +46,7 @@ async fn main() { cb(event); } } else { - async_std::task::sleep(time::Duration::from_millis(50)); + async_std::task::sleep(time::Duration::from_millis(50)).await; } } }); diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index 313395fe9..24746064f 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -565,15 +565,18 @@ class IOThreads: self._thread_quitflag = False self._name2thread = {} self._log_event = log_event - self._running = False + self._log_running = True + # Make sure the current + self._start_one_thread("deltachat-log", self.dc_thread_run) + def is_started(self): - return self._running + return lib.dc_is_open(self._dc_context) and lib.dc_is_running(self._dc_context) def start(self, imap=True, smtp=True, mvbox=False, sentbox=False): assert not self.is_started() - self._running = True - self._start_one_thread("deltachat", self.dc_thread_run) + + lib.dc_context_run(self._dc_context) def _start_one_thread(self, name, func): self._name2thread[name] = t = threading.Thread(target=func, name=name) @@ -581,18 +584,14 @@ class IOThreads: t.start() def stop(self, wait=False): - lib.dc_context_shutdown(self._dc_context) - if wait: - for name, thread in self._name2thread.items(): - thread.join() - self._running = False + if self.is_started(): + lib.dc_context_shutdown(self._dc_context) def dc_thread_run(self): - self._log_event("py-bindings-info", 0, "DC THREAD START") + self._log_event("py-bindings-info", 0, "DC LOG THREAD START") - lib.dc_context_run(self._dc_context) - while self._running: - if lib.dc_has_next_event(self._dc_context): + while self._log_running: + if lib.dc_is_open(self._dc_context) and lib.dc_has_next_event(self._dc_context): event = lib.dc_get_next_event(self._dc_context) if event != ffi.NULL: deltachat.py_dc_callback( @@ -605,7 +604,7 @@ class IOThreads: else: time.sleep(0.05) - self._log_event("py-bindings-info", 0, "DC THREAD FINISHED") + self._log_event("py-bindings-info", 0, "DC LOG THREAD FINISHED") def _destroy_dc_context(dc_context, dc_context_unref=lib.dc_context_unref): # destructor for dc_context diff --git a/python/tests/conftest.py b/python/tests/conftest.py index b6868bc2c..4dc3781af 100644 --- a/python/tests/conftest.py +++ b/python/tests/conftest.py @@ -223,7 +223,7 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, datadir): pre_generated_key=pre_generated_key) configdict.update(config) ac.configure(**configdict) - ac.start_threads(mvbox=mvbox, sentbox=sentbox) + ac.start_threads() return ac def get_one_online_account(self, pre_generated_key=True): diff --git a/python/tests/test_account.py b/python/tests/test_account.py index 1b868e191..d95486e03 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -178,7 +178,6 @@ class TestOfflineChat: assert d["draft"] == "" if chat.get_draft() is None else chat.get_draft() def test_group_chat_creation_with_translation(self, ac1): - ac1.start_threads() ac1.set_stock_translation(const.DC_STR_NEWGROUPDRAFT, "xyz %1$s") ac1._evlogger.consume_events() with pytest.raises(ValueError): @@ -198,7 +197,6 @@ class TestOfflineChat: assert not chat.is_promoted() msg = chat.get_draft() assert msg.text == "xyz title1" - ac1.stop_threads() @pytest.mark.parametrize("verified", [True, False]) def test_group_chat_qr(self, acfactory, ac1, verified): @@ -351,6 +349,7 @@ class TestOfflineChat: ac1.configure(addr="123@example.org") def test_import_export_one_contact(self, acfactory, tmpdir): + print("START") backupdir = tmpdir.mkdir("backup") ac1 = acfactory.get_configured_offline_account() contact1 = ac1.create_contact("some1@hello.com", name="some1") @@ -362,24 +361,27 @@ class TestOfflineChat: with bin.open("w") as f: f.write("\00123" * 10000) msg = chat.send_file(bin.strpath) - + print("L1") contact = msg.get_sender_contact() assert contact == ac1.get_self_contact() assert not backupdir.listdir() - + print("L2") path = ac1.export_all(backupdir.strpath) assert os.path.exists(path) ac2 = acfactory.get_unconfigured_account() ac2.import_all(path) contacts = ac2.get_contacts(query="some1") assert len(contacts) == 1 + print("L3") contact2 = contacts[0] assert contact2.addr == "some1@hello.com" chat2 = ac2.create_chat_by_contact(contact2) messages = chat2.get_messages() assert len(messages) == 2 + print("L4") assert messages[0].text == "msg1" assert os.path.exists(messages[1].filename) + print("STOP") def test_ac_setup_message_fails(self, ac1): with pytest.raises(RuntimeError): diff --git a/python/tests/test_lowlevel.py b/python/tests/test_lowlevel.py index 60640a228..70cdc2b6c 100644 --- a/python/tests/test_lowlevel.py +++ b/python/tests/test_lowlevel.py @@ -131,8 +131,7 @@ def test_sig(): def test_markseen_invalid_message_ids(acfactory): ac1 = acfactory.get_configured_offline_account() - - ac1.start_threads() + contact1 = ac1.create_contact(email="some1@example.com", name="some1") chat = ac1.create_chat_by_contact(contact1) chat.send_text("one messae") @@ -140,7 +139,6 @@ def test_markseen_invalid_message_ids(acfactory): msg_ids = [9] lib.dc_markseen_msgs(ac1._dc_context, msg_ids, len(msg_ids)) ac1._evlogger.ensure_event_not_queued("DC_EVENT_WARNING|DC_EVENT_ERROR") - ac1.stop_threads() def test_get_special_message_id_returns_empty_message(acfactory): diff --git a/src/scheduler.rs b/src/scheduler.rs index 3af84f15b..46c196756 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -48,6 +48,8 @@ impl Context { } async fn inbox_loop(ctx: Context, inbox_handlers: ImapConnectionHandlers) { + use futures::future::FutureExt; + info!(ctx, "starting inbox loop"); let ImapConnectionHandlers { mut connection, @@ -56,7 +58,10 @@ async fn inbox_loop(ctx: Context, inbox_handlers: ImapConnectionHandlers) { } = inbox_handlers; let fut = async move { - connection.connect_configured(&ctx).await.unwrap(); + if let Err(err) = connection.connect_configured(&ctx).await { + error!(ctx, "{}", err); + return; + } loop { let probe_network = ctx.scheduler.read().await.get_probe_network(); @@ -101,7 +106,7 @@ async fn inbox_loop(ctx: Context, inbox_handlers: ImapConnectionHandlers) { } }; - fut.race(stop_receiver.recv()).await; + fut.race(stop_receiver.recv().map(|_| ())).await; shutdown_sender.send(()).await; } @@ -110,6 +115,8 @@ async fn simple_imap_loop( inbox_handlers: ImapConnectionHandlers, folder: impl AsRef, ) { + use futures::future::FutureExt; + info!(ctx, "starting simple loop for {}", folder.as_ref()); let ImapConnectionHandlers { mut connection, @@ -118,7 +125,10 @@ async fn simple_imap_loop( } = inbox_handlers; let fut = async move { - connection.connect_configured(&ctx).await.unwrap(); + if let Err(err) = connection.connect_configured(&ctx).await { + error!(ctx, "{}", err); + return; + } loop { match get_watch_folder(&ctx, folder.as_ref()).await { @@ -155,7 +165,7 @@ async fn simple_imap_loop( } }; - fut.race(stop_receiver.recv()).await; + fut.race(stop_receiver.recv().map(|_| ())).await; shutdown_sender.send(()).await; } From b616a2b3e716c08144599d30cdc467d221dce570 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Sun, 22 Mar 2020 21:57:26 +0100 Subject: [PATCH 029/118] fix more tests --- python/tests/conftest.py | 7 +++- python/tests/test_account.py | 34 +++++++++++------ python/tests/test_increation.py | 6 +++ src/configure/mod.rs | 68 ++++++++++++++++++--------------- src/context.rs | 34 ++++++++++------- src/imex.rs | 31 +++++++++------ src/securejoin.rs | 19 +++++++-- 7 files changed, 126 insertions(+), 73 deletions(-) diff --git a/python/tests/conftest.py b/python/tests/conftest.py index 4dc3781af..87b959001 100644 --- a/python/tests/conftest.py +++ b/python/tests/conftest.py @@ -223,7 +223,6 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, datadir): pre_generated_key=pre_generated_key) configdict.update(config) ac.configure(**configdict) - ac.start_threads() return ac def get_one_online_account(self, pre_generated_key=True): @@ -231,6 +230,7 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, datadir): pre_generated_key=pre_generated_key) wait_successful_IMAP_SMTP_connection(ac1) wait_configuration_progress(ac1, 1000) + ac1.start_threads() return ac1 def get_two_online_accounts(self): @@ -238,8 +238,11 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, datadir): ac2 = self.get_online_configuring_account() wait_successful_IMAP_SMTP_connection(ac1) wait_configuration_progress(ac1, 1000) + ac1.start_threads() wait_successful_IMAP_SMTP_connection(ac2) wait_configuration_progress(ac2, 1000) + ac2.start_threads() + return ac1, ac2 def clone_online_account(self, account, pre_generated_key=True): @@ -251,7 +254,7 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, datadir): ac._evlogger.init_time = self.init_time ac._evlogger.set_timeout(30) ac.configure(addr=account.get_config("addr"), mail_pw=account.get_config("mail_pw")) - ac.start_threads() + return ac am = AccountMaker() diff --git a/python/tests/test_account.py b/python/tests/test_account.py index d95486e03..b437afef0 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -349,7 +349,6 @@ class TestOfflineChat: ac1.configure(addr="123@example.org") def test_import_export_one_contact(self, acfactory, tmpdir): - print("START") backupdir = tmpdir.mkdir("backup") ac1 = acfactory.get_configured_offline_account() contact1 = ac1.create_contact("some1@hello.com", name="some1") @@ -361,27 +360,22 @@ class TestOfflineChat: with bin.open("w") as f: f.write("\00123" * 10000) msg = chat.send_file(bin.strpath) - print("L1") contact = msg.get_sender_contact() assert contact == ac1.get_self_contact() assert not backupdir.listdir() - print("L2") path = ac1.export_all(backupdir.strpath) assert os.path.exists(path) ac2 = acfactory.get_unconfigured_account() ac2.import_all(path) contacts = ac2.get_contacts(query="some1") assert len(contacts) == 1 - print("L3") contact2 = contacts[0] assert contact2.addr == "some1@hello.com" chat2 = ac2.create_chat_by_contact(contact2) messages = chat2.get_messages() assert len(messages) == 2 - print("L4") assert messages[0].text == "msg1" assert os.path.exists(messages[1].filename) - print("STOP") def test_ac_setup_message_fails(self, ac1): with pytest.raises(RuntimeError): @@ -447,7 +441,9 @@ class TestOnlineAccount: config={"key_gen_type": str(const.DC_KEY_GEN_ED25519)} ) wait_configuration_progress(ac1, 1000) + ac1.start_threads() wait_configuration_progress(ac2, 1000) + ac2.start_threads() chat = self.get_chat(ac1, ac2, both_created=True) lp.sec("ac1: send unencrypted message to ac2") @@ -482,6 +478,9 @@ class TestOnlineAccount: def test_export_import_self_keys(self, acfactory, tmpdir): ac1, ac2 = acfactory.get_two_online_accounts() + ac1.stop_threads() + ac2.stop_threads() + dir = tmpdir.mkdir("exportdir") export_files = ac1.export_self_keys(dir.strpath) assert len(export_files) == 2 @@ -499,8 +498,11 @@ class TestOnlineAccount: ac1_clone = acfactory.clone_online_account(ac1) wait_configuration_progress(ac1, 1000) + ac1.start_threads() wait_configuration_progress(ac2, 1000) + ac2.start_threads() wait_configuration_progress(ac1_clone, 1000) + ac1_clone.start_threads() chat = self.get_chat(ac1, ac2) @@ -605,10 +607,12 @@ class TestOnlineAccount: lp.sec("ac2: waiting for configuration") wait_configuration_progress(ac2, 1000) - + ac2.start_threads() + lp.sec("ac1: waiting for configuration") wait_configuration_progress(ac1, 1000) - + ac1.start_threads() + lp.sec("ac1: send message and wait for ac2 to receive it") chat = self.get_chat(ac1, ac2) chat.send_text("message1") @@ -620,7 +624,9 @@ class TestOnlineAccount: ac1 = acfactory.get_online_configuring_account() ac2 = acfactory.get_online_configuring_account(mvbox=True) wait_configuration_progress(ac2, 1000) + ac2.start_threads() wait_configuration_progress(ac1, 1000) + ac1.start_threads() chat = self.get_chat(ac1, ac2) chat.send_text("message1") ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED") @@ -632,7 +638,9 @@ class TestOnlineAccount: ac1.set_config("bcc_self", "1") ac2 = acfactory.get_online_configuring_account() wait_configuration_progress(ac2, 1000) + ac2.start_threads() wait_configuration_progress(ac1, 1000) + ac1.start_threads() chat = self.get_chat(ac1, ac2) chat.send_text("message1") chat.send_text("message2") @@ -979,6 +987,7 @@ class TestOnlineAccount: def test_import_export_online_all(self, acfactory, tmpdir, lp): ac1 = acfactory.get_online_configuring_account() wait_configuration_progress(ac1, 1000) + ac1.start_threads() lp.sec("create some chat content") contact1 = ac1.create_contact("some1@hello.com", name="some1") @@ -1027,7 +1036,9 @@ class TestOnlineAccount: ac1 = acfactory.get_online_configuring_account() ac2 = acfactory.clone_online_account(ac1) wait_configuration_progress(ac2, 1000) + ac2.start_threads() wait_configuration_progress(ac1, 1000) + ac1.start_threads() lp.sec("trigger ac setup message and return setupcode") assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"] setup_code = ac1.initiate_key_transfer() @@ -1050,7 +1061,9 @@ class TestOnlineAccount: ac2 = acfactory.clone_online_account(ac1) ac2._evlogger.set_timeout(30) wait_configuration_progress(ac2, 1000) + ac2.start_threads() wait_configuration_progress(ac1, 1000) + ac1.start_threads() lp.sec("trigger ac setup message but ignore") assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"] @@ -1279,6 +1292,7 @@ class TestGroupStressTests: accounts = [acfactory.get_online_configuring_account() for i in range(5)] for acc in accounts: wait_configuration_progress(acc, 1000) + acc.start_threads() ac1 = accounts.pop() lp.sec("ac1: setting up contacts with 4 other members") @@ -1382,6 +1396,7 @@ class TestGroupStressTests: accounts = [acfactory.get_online_configuring_account() for i in range(3)] for acc in accounts: wait_configuration_progress(acc, 1000) + acc.start_threads() ac1 = accounts.pop() lp.sec("ac1: setting up contacts with 2 other members") @@ -1449,7 +1464,6 @@ class TestOnlineConfigureFails: def test_invalid_password(self, acfactory): ac1, configdict = acfactory.get_online_config() ac1.configure(addr=configdict["addr"], mail_pw="123") - ac1.start_threads() wait_configuration_progress(ac1, 500) ev1 = ac1._evlogger.get_matching("DC_EVENT_ERROR_NETWORK") assert "cannot login" in ev1[2].lower() @@ -1458,7 +1472,6 @@ class TestOnlineConfigureFails: def test_invalid_user(self, acfactory): ac1, configdict = acfactory.get_online_config() ac1.configure(addr="x" + configdict["addr"], mail_pw=configdict["mail_pw"]) - ac1.start_threads() wait_configuration_progress(ac1, 500) ev1 = ac1._evlogger.get_matching("DC_EVENT_ERROR_NETWORK") assert "cannot login" in ev1[2].lower() @@ -1467,7 +1480,6 @@ class TestOnlineConfigureFails: def test_invalid_domain(self, acfactory): ac1, configdict = acfactory.get_online_config() ac1.configure(addr=configdict["addr"] + "x", mail_pw=configdict["mail_pw"]) - ac1.start_threads() wait_configuration_progress(ac1, 500) ev1 = ac1._evlogger.get_matching("DC_EVENT_ERROR_NETWORK") assert "could not connect" in ev1[2].lower() diff --git a/python/tests/test_increation.py b/python/tests/test_increation.py index 0b6735dda..4c31dcaa0 100644 --- a/python/tests/test_increation.py +++ b/python/tests/test_increation.py @@ -15,7 +15,9 @@ class TestOnlineInCreation: ac1 = acfactory.get_online_configuring_account() ac2 = acfactory.get_online_configuring_account() wait_configuration_progress(ac1, 1000) + ac1.start_threads() wait_configuration_progress(ac2, 1000) + ac2.start_threads() c2 = ac1.create_contact(email=ac2.get_config("addr")) chat = ac1.create_chat_by_contact(c2) @@ -30,7 +32,9 @@ class TestOnlineInCreation: ac1 = acfactory.get_online_configuring_account() ac2 = acfactory.get_online_configuring_account() wait_configuration_progress(ac1, 1000) + ac1.start_threads() wait_configuration_progress(ac2, 1000) + ac2.start_threads() c2 = ac1.create_contact(email=ac2.get_config("addr")) chat = ac1.create_chat_by_contact(c2) @@ -48,7 +52,9 @@ class TestOnlineInCreation: ac1 = acfactory.get_online_configuring_account() ac2 = acfactory.get_online_configuring_account() wait_configuration_progress(ac1, 1000) + ac1.start_threads() wait_configuration_progress(ac2, 1000) + ac2.start_threads() c2 = ac1.create_contact(email=ac2.get_config("addr")) chat = ac1.create_chat_by_contact(c2) diff --git a/src/configure/mod.rs b/src/configure/mod.rs index 92b4b32ec..43bf1f96a 100644 --- a/src/configure/mod.rs +++ b/src/configure/mod.rs @@ -4,6 +4,7 @@ mod auto_mozilla; mod auto_outlook; mod read_url; +use async_std::prelude::*; use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; use crate::config::Config; @@ -13,11 +14,11 @@ use crate::dc_tools::*; use crate::error::{Error, Result}; use crate::imap::Imap; use crate::login_param::{CertificateChecks, LoginParam}; +use crate::message::Message; use crate::oauth2::*; use crate::smtp::Smtp; use crate::{chat, e2ee, provider}; -use crate::message::Message; use auto_mozilla::moz_autoconfigure; use auto_outlook::outlk_autodiscover; @@ -39,10 +40,8 @@ impl Context { /// Configures this account with the currently set parameters. pub async fn configure(&self) -> Result<()> { - ensure!( - !self.has_ongoing().await, - "There is already another ongoing process running." - ); + use futures::future::FutureExt; + ensure!( !self.scheduler.read().await.is_running(), "Can not configure, already running" @@ -51,11 +50,22 @@ impl Context { self.sql.is_open().await, "Cannot configure, database not opened." ); - ensure!( - self.alloc_ongoing().await, - "Cannot allocate ongoing process" - ); + let cancel_channel = self.alloc_ongoing().await?; + let res = self + .inner_configure() + .race(cancel_channel.recv().map(|_| { + progress!(self, 0); + Ok(()) + })) + .await; + + self.free_ongoing().await; + + res + } + + async fn inner_configure(&self) -> Result<()> { let mut success = false; let mut param_autoconfig: Option = None; @@ -127,15 +137,12 @@ impl Context { // and restore to last-entered on failure. // this way, the parameters visible to the ui are always in-sync with the current configuration. if success { - assert!(self.is_configured().await, "epic fail"); LoginParam::from_database(self, "") .await .save_to_database(self, "configured_raw_") .await .ok(); - self.free_ongoing().await; - progress!(self, 1000); Ok(()) } else { @@ -145,8 +152,6 @@ impl Context { .await .ok(); - self.free_ongoing().await; - progress!(self, 0); Err(Error::Message("Configure failed".to_string())) } @@ -398,8 +403,8 @@ async fn exec_step( progress!(ctx, 600); /* try to connect to IMAP - if we did not got an autoconfig, do some further tries with different settings and username variations */ - try_imap_connections(ctx, param, param_autoconfig.is_some(), imap).await?; - *is_imap_connected = true; + *is_imap_connected = + try_imap_connections(ctx, param, param_autoconfig.is_some(), imap).await?; } 15 => { progress!(ctx, 800); @@ -512,13 +517,10 @@ async fn try_imap_connections( mut param: &mut LoginParam, was_autoconfig: bool, imap: &mut Imap, -) -> Result<()> { +) -> Result { // progress 650 and 660 - if try_imap_connection(context, &mut param, was_autoconfig, 0, imap) - .await - .is_ok() - { - return Ok(()); + if let Ok(val) = try_imap_connection(context, &mut param, was_autoconfig, 0, imap).await { + return Ok(val); } progress!(context, 670); param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS); @@ -532,9 +534,7 @@ async fn try_imap_connections( param.send_user = param.send_user.split_at(at).0.to_string(); } // progress 680 and 690 - try_imap_connection(context, &mut param, was_autoconfig, 1, imap).await?; - - Ok(()) + try_imap_connection(context, &mut param, was_autoconfig, 1, imap).await } async fn try_imap_connection( @@ -543,24 +543,26 @@ async fn try_imap_connection( was_autoconfig: bool, variation: usize, imap: &mut Imap, -) -> Result<()> { +) -> Result { if try_imap_one_param(context, ¶m, imap).await.is_ok() { - return Ok(()); + return Ok(true); } if was_autoconfig { - bail!("autoconfig"); + return Ok(false); } progress!(context, 650 + variation * 30); param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS); param.server_flags |= DC_LP_IMAP_SOCKET_STARTTLS; if try_imap_one_param(context, ¶m, imap).await.is_ok() { - return Ok(()); + return Ok(true); } progress!(context, 660 + variation * 30); param.mail_port = 143; - try_imap_one_param(context, ¶m, imap).await + try_imap_one_param(context, ¶m, imap).await?; + + Ok(true) } async fn try_imap_one_param(context: &Context, param: &LoginParam, imap: &mut Imap) -> Result<()> { @@ -579,6 +581,10 @@ async fn try_imap_one_param(context: &Context, param: &LoginParam, imap: &mut Im return Ok(()); } + if context.shall_stop_ongoing().await { + bail!("Interrupted"); + } + bail!("Could not connect: {}", inf); } @@ -593,7 +599,7 @@ async fn try_smtp_connections( return Ok(()); } if was_autoconfig { - bail!("autoconfig"); + return Ok(()); } progress!(context, 850); param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32); diff --git a/src/context.rs b/src/context.rs index 85ea121f6..5158508c2 100644 --- a/src/context.rs +++ b/src/context.rs @@ -5,7 +5,7 @@ use std::ffi::OsString; use std::ops::Deref; use async_std::path::{Path, PathBuf}; -use async_std::sync::{Arc, Mutex, RwLock}; +use async_std::sync::{channel, Arc, Mutex, Receiver, RwLock, Sender}; use crossbeam_queue::SegQueue; use crate::chat::*; @@ -55,10 +55,11 @@ pub struct InnerContext { pub(crate) scheduler: RwLock, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug)] pub struct RunningState { pub ongoing_running: bool, shall_stop_ongoing: bool, + cancel_sender: Option>, } /// Return some info about deltachat-core @@ -180,20 +181,20 @@ impl Context { * Ongoing process allocation/free/check ******************************************************************************/ - pub async fn alloc_ongoing(&self) -> bool { + pub async fn alloc_ongoing(&self) -> Result> { if self.has_ongoing().await { - warn!(self, "There is already another ongoing process running.",); - - false - } else { - let s_a = &self.running_state; - let mut s = s_a.write().await; - - s.ongoing_running = true; - s.shall_stop_ongoing = false; - - true + bail!("There is already another ongoing process running."); } + + let s_a = &self.running_state; + let mut s = s_a.write().await; + + s.ongoing_running = true; + s.shall_stop_ongoing = false; + let (sender, receiver) = channel(1); + s.cancel_sender = Some(sender); + + Ok(receiver) } pub async fn free_ongoing(&self) { @@ -202,6 +203,7 @@ impl Context { s.ongoing_running = false; s.shall_stop_ongoing = true; + s.cancel_sender.take(); } pub async fn has_ongoing(&self) -> bool { @@ -215,6 +217,9 @@ impl Context { pub async fn stop_ongoing(&self) { let s_a = &self.running_state; let mut s = s_a.write().await; + if let Some(cancel) = s.cancel_sender.take() { + cancel.send(()).await; + } if s.ongoing_running && !s.shall_stop_ongoing { info!(self, "Signaling the ongoing process to stop ASAP.",); @@ -503,6 +508,7 @@ impl Default for RunningState { RunningState { ongoing_running: false, shall_stop_ongoing: true, + cancel_sender: None, } } } diff --git a/src/imex.rs b/src/imex.rs index 6d3a152ac..04a054555 100644 --- a/src/imex.rs +++ b/src/imex.rs @@ -70,9 +70,16 @@ pub async fn imex( what: ImexMode, param1: Option>, ) -> Result<()> { - job_imex_imap(context, what, param1).await?; + use futures::future::FutureExt; - Ok(()) + let cancel = context.alloc_ongoing().await?; + let res = imex_inner(context, what, param1) + .race(cancel.recv().map(|_| Err(format_err!("canceled")))) + .await; + + context.free_ongoing().await; + + res } /// Returns the filename of the backup found (otherwise an error) @@ -110,8 +117,13 @@ pub async fn has_backup(context: &Context, dir_name: impl AsRef) -> Result } pub async fn initiate_key_transfer(context: &Context) -> Result { - ensure!(context.alloc_ongoing().await, "could not allocate ongoing"); - let res = do_initiate_key_transfer(context).await; + use futures::future::FutureExt; + + let cancel = context.alloc_ongoing().await?; + let res = do_initiate_key_transfer(context) + .race(cancel.recv().map(|_| Err(format_err!("canceled")))) + .await; + context.free_ongoing().await; res } @@ -120,10 +132,8 @@ async fn do_initiate_key_transfer(context: &Context) -> Result { let mut msg: Message; let setup_code = create_setup_code(context); /* this may require a keypair to be created. this may take a second ... */ - ensure!(!context.shall_stop_ongoing().await, "canceled"); let setup_file_content = render_setup_file(context, &setup_code).await?; /* encrypting may also take a while ... */ - ensure!(!context.shall_stop_ongoing().await, "canceled"); let setup_file_blob = BlobObject::create( context, "autocrypt-setup-message.html", @@ -144,7 +154,6 @@ async fn do_initiate_key_transfer(context: &Context) -> Result { ForcePlaintext::NoAutocryptHeader as i32, ); - ensure!(!context.shall_stop_ongoing().await, "canceled"); let msg_id = chat::send_msg(context, chat_id, &mut msg).await?; info!(context, "Wait for setup message being sent ...",); while !context.shall_stop_ongoing().await { @@ -363,13 +372,12 @@ pub fn normalize_setup_code(s: &str) -> String { out } -pub async fn job_imex_imap( +async fn imex_inner( context: &Context, what: ImexMode, param: Option>, ) -> Result<()> { - ensure!(context.alloc_ongoing().await, "could not allocate ongoing"); - ensure!(!param.is_some(), "No Import/export dir/file given."); + ensure!(param.is_some(), "No Import/export dir/file given."); info!(context, "Import/export process started."); context.call_cb(Event::ImexProgress(10)); @@ -380,7 +388,6 @@ pub async fn job_imex_imap( if what == ImexMode::ExportBackup || what == ImexMode::ExportSelfKeys { // before we export anything, make sure the private key exists if e2ee::ensure_secret_key_exists(context).await.is_err() { - context.free_ongoing().await; bail!("Cannot create private key or private key not available."); } else { dc_create_folder(context, &path).await?; @@ -393,7 +400,7 @@ pub async fn job_imex_imap( ImexMode::ExportBackup => export_backup(context, path).await, ImexMode::ImportBackup => import_backup(context, path).await, }; - context.free_ongoing().await; + match success { Ok(()) => { info!(context, "IMEX successfully completed"); diff --git a/src/securejoin.rs b/src/securejoin.rs index f6c445009..486f70940 100644 --- a/src/securejoin.rs +++ b/src/securejoin.rs @@ -1,5 +1,6 @@ //! Verified contact protocol implementation as [specified by countermitm project](https://countermitm.readthedocs.io/en/stable/new.html#setup-contact-protocol) +use async_std::prelude::*; use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC}; use crate::aheader::EncryptPreference; @@ -181,6 +182,21 @@ async fn cleanup( /// Take a scanned QR-code and do the setup-contact/join-group handshake. /// See the ffi-documentation for more details. pub async fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId { + use futures::future::FutureExt; + + let cancel = match context.alloc_ongoing().await { + Ok(cancel) => cancel, + Err(_) => { + return cleanup(&context, ChatId::new(0), false, false).await; + } + }; + + securejoin(context, qr) + .race(cancel.recv().map(|_| ChatId::new(0))) + .await +} + +async fn securejoin(context: &Context, qr: &str) -> ChatId { /*======================================================== ==== Bob - the joiner's side ===== ==== Step 2 in "Setup verified contact" protocol ===== @@ -191,9 +207,6 @@ pub async fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId { info!(context, "Requesting secure-join ...",); ensure_secret_key_exists(context).await.ok(); - if !context.alloc_ongoing().await { - return cleanup(&context, contact_chat_id, false, join_vg).await; - } let qr_scan = check_qr(context, &qr).await; if qr_scan.state != LotState::QrAskVerifyContact && qr_scan.state != LotState::QrAskVerifyGroup { From d50c1e365853e099d2e160e27d9d225ad4345a6a Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Sun, 22 Mar 2020 22:05:37 +0100 Subject: [PATCH 030/118] fixes from merge with master --- src/dc_receive_imf.rs | 42 ++++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index 01590ef1b..339b9696d 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -1780,37 +1780,35 @@ mod tests { ); } - #[test] - fn test_is_known_rfc724_mid() { - let t = dummy_context(); + #[async_std::test] + async fn test_is_known_rfc724_mid() { + let t = dummy_context().await; let mut msg = Message::new(Viewtype::Text); msg.text = Some("first message".to_string()); - let msg_id = chat::add_device_msg(&t.ctx, None, Some(&mut msg)).unwrap(); - let msg = Message::load_from_db(&t.ctx, msg_id).unwrap(); + let msg_id = chat::add_device_msg(&t.ctx, None, Some(&mut msg)) + .await + .unwrap(); + let msg = Message::load_from_db(&t.ctx, msg_id).await.unwrap(); // Message-IDs may or may not be surrounded by angle brackets - assert!(is_known_rfc724_mid( - &t.ctx, - format!("<{}>", msg.rfc724_mid).as_str() - )); - assert!(is_known_rfc724_mid(&t.ctx, &msg.rfc724_mid)); - assert!(!is_known_rfc724_mid(&t.ctx, "nonexistant@message.id")); + assert!(is_known_rfc724_mid(&t.ctx, format!("<{}>", msg.rfc724_mid).as_str()).await); + assert!(is_known_rfc724_mid(&t.ctx, &msg.rfc724_mid).await); + assert!(!is_known_rfc724_mid(&t.ctx, "nonexistant@message.id").await); } - #[test] - fn test_is_msgrmsg_rfc724_mid() { - let t = dummy_context(); + #[async_std::test] + async fn test_is_msgrmsg_rfc724_mid() { + let t = dummy_context().await; let mut msg = Message::new(Viewtype::Text); msg.text = Some("first message".to_string()); - let msg_id = chat::add_device_msg(&t.ctx, None, Some(&mut msg)).unwrap(); - let msg = Message::load_from_db(&t.ctx, msg_id).unwrap(); + let msg_id = chat::add_device_msg(&t.ctx, None, Some(&mut msg)) + .await + .unwrap(); + let msg = Message::load_from_db(&t.ctx, msg_id).await.unwrap(); // Message-IDs may or may not be surrounded by angle brackets - assert!(is_msgrmsg_rfc724_mid( - &t.ctx, - format!("<{}>", msg.rfc724_mid).as_str() - )); - assert!(is_msgrmsg_rfc724_mid(&t.ctx, &msg.rfc724_mid)); - assert!(!is_msgrmsg_rfc724_mid(&t.ctx, "nonexistant@message.id")); + assert!(is_msgrmsg_rfc724_mid(&t.ctx, format!("<{}>", msg.rfc724_mid).as_str()).await); + assert!(is_msgrmsg_rfc724_mid(&t.ctx, &msg.rfc724_mid).await); + assert!(!is_msgrmsg_rfc724_mid(&t.ctx, "nonexistant@message.id").await); } } From c9742bb6ea85c6fce6cec91dea379a3d9d45b61f Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Sun, 22 Mar 2020 22:13:02 +0100 Subject: [PATCH 031/118] update toolchain for gh actions --- .github/workflows/code-quality.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index bda5d7001..bc45cbd9b 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -10,7 +10,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: nightly-2019-11-06 + toolchain: nightly-2020-03-04 override: true - uses: actions-rs/cargo@v1 with: @@ -25,7 +25,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: nightly-2019-11-06 + toolchain: nightly-2020-03-04 override: true - run: rustup component add rustfmt - uses: actions-rs/cargo@v1 @@ -39,10 +39,10 @@ jobs: - uses: actions/checkout@v1 - uses: actions-rs/toolchain@v1 with: - toolchain: nightly-2019-11-06 + toolchain: nightly-2020-03-04 components: clippy override: true - uses: actions-rs/clippy-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} - args: --all-features \ No newline at end of file + args: --all-features From 0ead27a05be3d3f373e992d20c516f448ff3eadd Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Sun, 22 Mar 2020 22:33:07 +0100 Subject: [PATCH 032/118] update toolchain --- .github/workflows/code-quality.yml | 6 +++--- appveyor.yml | 2 +- ci_scripts/docker-coredeps/deps/build_rust.sh | 4 ++-- rust-toolchain | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index bc45cbd9b..a05066102 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -10,7 +10,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: nightly-2020-03-04 + toolchain: nightly-2020-03-19 override: true - uses: actions-rs/cargo@v1 with: @@ -25,7 +25,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: nightly-2020-03-04 + toolchain: nightly-2020-03-19 override: true - run: rustup component add rustfmt - uses: actions-rs/cargo@v1 @@ -39,7 +39,7 @@ jobs: - uses: actions/checkout@v1 - uses: actions-rs/toolchain@v1 with: - toolchain: nightly-2020-03-04 + toolchain: nightly-2020-03-19 components: clippy override: true - uses: actions-rs/clippy-check@v1 diff --git a/appveyor.yml b/appveyor.yml index 7276acac1..b8ea374f3 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,7 +4,7 @@ environment: install: - appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe - - rustup-init -yv --default-toolchain nightly-2019-07-10 + - rustup-init -yv --default-toolchain nightly-2020-03-19 - set PATH=%PATH%;%USERPROFILE%\.cargo\bin - rustc -vV - cargo -vV diff --git a/ci_scripts/docker-coredeps/deps/build_rust.sh b/ci_scripts/docker-coredeps/deps/build_rust.sh index 3e0a232c4..93e8ed281 100755 --- a/ci_scripts/docker-coredeps/deps/build_rust.sh +++ b/ci_scripts/docker-coredeps/deps/build_rust.sh @@ -3,9 +3,9 @@ set -e -x # Install Rust -curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly-2019-11-06 -y +curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly-2020-03-19 -y export PATH=/root/.cargo/bin:$PATH rustc --version # remove some 300-400 MB that we don't need for automated builds -rm -rf /root/.rustup/toolchains/nightly-2019-11-06-x86_64-unknown-linux-gnu/share/ +rm -rf /root/.rustup/toolchains/nightly-2020-03-19-x86_64-unknown-linux-gnu/share/ diff --git a/rust-toolchain b/rust-toolchain index 5b1388415..10a652fd3 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2020-03-04 +nightly-2020-03-19 From 01b88f876e32a45974fb6880050e2026a442971c Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Mon, 23 Mar 2020 00:09:22 +0100 Subject: [PATCH 033/118] fix idle interrupts --- Cargo.lock | 30 ++++++++++++++++++++++++++++-- Cargo.toml | 4 ++-- src/scheduler.rs | 3 ++- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bbb9bcd6b..a17a654ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -147,7 +147,7 @@ dependencies = [ [[package]] name = "async-std" version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" +source = "git+https://github.com/async-rs/async-std#2dbebe54ede4d2c0a18380f51e785d5306022ac5" dependencies = [ "async-attributes 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "async-task 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -170,6 +170,31 @@ dependencies = [ "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "async-std" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "async-task 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "broadcaster 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-io 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-timer 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "kv-log-macro 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)", + "mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "once_cell 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "async-task" version = "1.3.1" @@ -631,7 +656,7 @@ dependencies = [ "async-imap 0.2.0 (git+https://github.com/async-email/async-imap?branch=feat/send)", "async-native-tls 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "async-smtp 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "async-std 1.5.0 (git+https://github.com/async-rs/async-std)", "backtrace 0.3.45 (registry+https://github.com/rust-lang/crates.io-index)", "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3159,6 +3184,7 @@ dependencies = [ "checksum async-imap 0.2.0 (git+https://github.com/async-email/async-imap?branch=feat/send)" = "" "checksum async-native-tls 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9e9e7a929bd34c68a82d58a4de7f86fffdaf97fb2af850162a7bb19dd7269b33" "checksum async-smtp 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3652e5c6072c0694a2bcdb7e8409980d2676bd4f024adf4aab10c68fd2b48f5" +"checksum async-std 1.5.0 (git+https://github.com/async-rs/async-std)" = "" "checksum async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "538ecb01eb64eecd772087e5b6f7540cbc917f047727339a472dafed2185b267" "checksum async-task 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0ac2c016b079e771204030951c366db398864f5026f84a44dafb0ff20f02085d" "checksum async-trait 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)" = "750b1c38a1dfadd108da0f01c08f4cdc7ff1bb39b325f9c82cc972361780a6e1" diff --git a/Cargo.toml b/Cargo.toml index ba9d61252..f91aaa853 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ email = { git = "https://github.com/deltachat/rust-email", branch = "master" } lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" } async-imap = { git = "https://github.com/async-email/async-imap", branch = "feat/send" } async-native-tls = "0.3.1" -async-std = { version = "1.4", features = ["unstable"] } +async-std = { git = "https://github.com/async-rs/async-std", version = "1.5", features = ["unstable"] } base64 = "0.11" charset = "0.1" percent-encoding = "2.0" @@ -71,7 +71,7 @@ tempfile = "3.0" pretty_assertions = "0.6.1" pretty_env_logger = "0.3.0" proptest = "0.9.4" -async-std = { version = "1.4", features = ["unstable", "attributes"] } +async-std = { git = "https://github.com/async-rs/async-std", version = "1.5", features = ["unstable", "attributes"] } [workspace] members = [ diff --git a/src/scheduler.rs b/src/scheduler.rs index 46c196756..9b6b1993c 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -366,7 +366,8 @@ impl ConnectionState { } async fn interrupt(&self) { - self.idle_interrupt_sender.send(()).await; + // Use try_send to avoid blocking on interrupts. + self.idle_interrupt_sender.try_send(()).ok(); } } From 69f1e1753c726c62290f50a5a2c1ff19667c2285 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Mon, 23 Mar 2020 19:49:37 +0100 Subject: [PATCH 034/118] improve logging and avoid race --- src/scheduler.rs | 7 +++++-- src/sql.rs | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/scheduler.rs b/src/scheduler.rs index 9b6b1993c..d51ad7aba 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -57,7 +57,9 @@ async fn inbox_loop(ctx: Context, inbox_handlers: ImapConnectionHandlers) { shutdown_sender, } = inbox_handlers; + let ctx1 = ctx.clone(); let fut = async move { + let ctx = ctx1; if let Err(err) = connection.connect_configured(&ctx).await { error!(ctx, "{}", err); return; @@ -106,6 +108,7 @@ async fn inbox_loop(ctx: Context, inbox_handlers: ImapConnectionHandlers) { } }; + info!(ctx, "Shutting down inbox loop"); fut.race(stop_receiver.recv().map(|_| ())).await; shutdown_sender.send(()).await; } @@ -201,7 +204,7 @@ async fn smtp_loop(ctx: Context, smtp_handlers: SmtpConnectionHandlers) { } }; - fut.race(stop_receiver.recv()).await; + fut.race(stop_receiver.recv()).await.ok(); shutdown_sender.send(()).await; } @@ -362,7 +365,7 @@ impl ConnectionState { // Trigger shutdown of the run loop. self.stop_sender.send(()).await; // Wait for a notification that the run loop has been shutdown. - self.shutdown_receiver.recv().await; + self.shutdown_receiver.recv().await.ok(); } async fn interrupt(&self) { diff --git a/src/sql.rs b/src/sql.rs index 0d84580e5..99dab7b06 100644 --- a/src/sql.rs +++ b/src/sql.rs @@ -181,7 +181,7 @@ impl Sql { let pool = lock.as_ref().ok_or_else(|| Error::SqlNoConnection)?; let conn = pool.get()?; - let res = async_std::task::spawn_blocking(move || g(conn)).await; + let res = g(conn); self.in_use.remove(); res @@ -419,7 +419,10 @@ impl Sql { let bt = backtrace::Backtrace::new(); eprintln!("old query: {}", query); eprintln!("Connection is already used from this thread: {:?}", bt); - panic!("Connection is already used from this thread"); + panic!( + "Connection is already used from this thread: trying to execute {}", + stmt.as_ref() + ); } self.in_use.set(stmt.as_ref().to_string()); From 3871c5a4a070ac2a90fe6bdeb65493421348d382 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 25 Mar 2020 13:48:02 +0100 Subject: [PATCH 035/118] fix some more python test --- python/tests/conftest.py | 4 ++-- python/tests/test_account.py | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/python/tests/conftest.py b/python/tests/conftest.py index 87b959001..14295fbeb 100644 --- a/python/tests/conftest.py +++ b/python/tests/conftest.py @@ -279,11 +279,11 @@ def lp(): return Printer() -def wait_configuration_progress(account, min_target, max_target=1001): +def wait_configuration_progress(account, min_target, max_target=1001, check_error=True): min_target = min(min_target, max_target) while 1: evt_name, data1, data2 = \ - account._evlogger.get_matching("DC_EVENT_CONFIGURE_PROGRESS") + account._evlogger.get_matching("DC_EVENT_CONFIGURE_PROGRESS", check_error=check_error) if data1 >= min_target and data1 <= max_target: print("** CONFIG PROGRESS {}".format(min_target), account) break diff --git a/python/tests/test_account.py b/python/tests/test_account.py index b437afef0..54d6a890a 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -1465,22 +1465,22 @@ class TestOnlineConfigureFails: ac1, configdict = acfactory.get_online_config() ac1.configure(addr=configdict["addr"], mail_pw="123") wait_configuration_progress(ac1, 500) - ev1 = ac1._evlogger.get_matching("DC_EVENT_ERROR_NETWORK") + ev1 = ac1._evlogger.get_matching("DC_EVENT_ERROR_NETWORK", check_error=False) assert "cannot login" in ev1[2].lower() - wait_configuration_progress(ac1, 0, 0) + wait_configuration_progress(ac1, 0, 0, check_error=False) def test_invalid_user(self, acfactory): ac1, configdict = acfactory.get_online_config() ac1.configure(addr="x" + configdict["addr"], mail_pw=configdict["mail_pw"]) wait_configuration_progress(ac1, 500) - ev1 = ac1._evlogger.get_matching("DC_EVENT_ERROR_NETWORK") + ev1 = ac1._evlogger.get_matching("DC_EVENT_ERROR_NETWORK", check_error=False) assert "cannot login" in ev1[2].lower() - wait_configuration_progress(ac1, 0, 0) + wait_configuration_progress(ac1, 0, 0, check_error=False) def test_invalid_domain(self, acfactory): ac1, configdict = acfactory.get_online_config() ac1.configure(addr=configdict["addr"] + "x", mail_pw=configdict["mail_pw"]) - wait_configuration_progress(ac1, 500) + wait_configuration_progress(ac1, 500, check_error=False) ev1 = ac1._evlogger.get_matching("DC_EVENT_ERROR_NETWORK") assert "could not connect" in ev1[2].lower() - wait_configuration_progress(ac1, 0, 0) + wait_configuration_progress(ac1, 0, 0, check_error=False) From 97951aec1597ff809ecdfac03325c4d31a091c1c Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 25 Mar 2020 15:41:27 +0100 Subject: [PATCH 036/118] implement imape secure upgrade --- Cargo.lock | 2 +- src/imap/client.rs | 23 +++++++++++++---------- src/imap/session.rs | 1 + 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a17a654ed..85cc6be72 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -93,7 +93,7 @@ dependencies = [ [[package]] name = "async-imap" version = "0.2.0" -source = "git+https://github.com/async-email/async-imap?branch=feat/send#be7b9cace12d0f323ef1e6e0a9fc698f4e26b64d" +source = "git+https://github.com/async-email/async-imap?branch=feat/send#94a48158d6cc622aa0ed99d10b84088e9e53eb2f" dependencies = [ "async-native-tls 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/src/imap/client.rs b/src/imap/client.rs index 923bc65ec..e93b273b8 100644 --- a/src/imap/client.rs +++ b/src/imap/client.rs @@ -120,21 +120,24 @@ impl Client { pub async fn secure>( self, - _domain: S, - _certificate_checks: CertificateChecks, + domain: S, + certificate_checks: CertificateChecks, ) -> ImapResult { if self.is_secure { Ok(self) } else { - unimplemented!() - // let Client { inner, .. } = self; - // let tls = dc_build_tls(certificate_checks); - // let client_sec = inner.secure(domain, tls).await?; + let Client { mut inner, .. } = self; + let tls = dc_build_tls(certificate_checks); + inner.run_command_and_check_ok("STARTTLS", None).await?; - // Ok(Client { - // is_secure: true, - // inner: client_sec, - // }) + let stream = inner.into_inner(); + let ssl_stream = tls.connect(domain.as_ref(), stream).await?; + let boxed: Box = Box::new(ssl_stream); + + Ok(Client { + is_secure: true, + inner: ImapClient::new(boxed), + }) } } } diff --git a/src/imap/session.rs b/src/imap/session.rs index 8b8a680a3..e533cc22e 100644 --- a/src/imap/session.rs +++ b/src/imap/session.rs @@ -14,6 +14,7 @@ pub(crate) trait SessionStream: { } +impl SessionStream for TlsStream> {} impl SessionStream for TlsStream {} impl SessionStream for TcpStream {} From f8cd602cbd58ca18f4523cdda0e81f6d4cd8dca2 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 25 Mar 2020 17:56:35 +0100 Subject: [PATCH 037/118] happy python lint --- python/src/deltachat/account.py | 7 ++++--- python/tests/conftest.py | 2 +- python/tests/test_account.py | 6 +++--- python/tests/test_lowlevel.py | 9 +++++---- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index 24746064f..7e41fcc11 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -569,13 +569,13 @@ class IOThreads: # Make sure the current self._start_one_thread("deltachat-log", self.dc_thread_run) - + def is_started(self): - return lib.dc_is_open(self._dc_context) and lib.dc_is_running(self._dc_context) + return lib.dc_is_open(self._dc_context) and lib.dc_is_running(self._dc_context) def start(self, imap=True, smtp=True, mvbox=False, sentbox=False): assert not self.is_started() - + lib.dc_context_run(self._dc_context) def _start_one_thread(self, name, func): @@ -606,6 +606,7 @@ class IOThreads: self._log_event("py-bindings-info", 0, "DC LOG THREAD FINISHED") + def _destroy_dc_context(dc_context, dc_context_unref=lib.dc_context_unref): # destructor for dc_context dc_context_unref(dc_context) diff --git a/python/tests/conftest.py b/python/tests/conftest.py index 14295fbeb..425a7f680 100644 --- a/python/tests/conftest.py +++ b/python/tests/conftest.py @@ -242,7 +242,7 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, datadir): wait_successful_IMAP_SMTP_connection(ac2) wait_configuration_progress(ac2, 1000) ac2.start_threads() - + return ac1, ac2 def clone_online_account(self, account, pre_generated_key=True): diff --git a/python/tests/test_account.py b/python/tests/test_account.py index 54d6a890a..e948fa8b7 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -480,7 +480,7 @@ class TestOnlineAccount: ac1, ac2 = acfactory.get_two_online_accounts() ac1.stop_threads() ac2.stop_threads() - + dir = tmpdir.mkdir("exportdir") export_files = ac1.export_self_keys(dir.strpath) assert len(export_files) == 2 @@ -608,11 +608,11 @@ class TestOnlineAccount: lp.sec("ac2: waiting for configuration") wait_configuration_progress(ac2, 1000) ac2.start_threads() - + lp.sec("ac1: waiting for configuration") wait_configuration_progress(ac1, 1000) ac1.start_threads() - + lp.sec("ac1: send message and wait for ac2 to receive it") chat = self.get_chat(ac1, ac2) chat.send_text("message1") diff --git a/python/tests/test_lowlevel.py b/python/tests/test_lowlevel.py index 70cdc2b6c..8186071d3 100644 --- a/python/tests/test_lowlevel.py +++ b/python/tests/test_lowlevel.py @@ -1,10 +1,10 @@ from __future__ import print_function import threading import time -from deltachat import capi, cutil, const, set_context_callback, clear_context_callback +from deltachat import capi, cutil, const, set_context_callback, clear_context_callback, py_dc_callback from deltachat.capi import ffi from deltachat.capi import lib -from deltachat.account import EventLogger +# from deltachat.account import EventLogger class EventThread(threading.Thread): @@ -16,11 +16,11 @@ class EventThread(threading.Thread): def run(self): lib.dc_context_run(self.dc_context) - while self._running: + while self._running: if lib.dc_has_next_event(self.dc_context): event = lib.dc_get_next_event(self.dc_context) if event != ffi.NULL: - deltachat.py_dc_callback( + py_dc_callback( self._dc_context, lib.dc_event_get_id(event), lib.dc_event_get_data1(event), @@ -46,6 +46,7 @@ def test_callback_None2int(): capi.lib.dc_close(ctx) clear_context_callback(ctx) + def test_start_stop_event_thread_basic(): print("1") ctx = capi.lib.dc_context_new(ffi.NULL, ffi.NULL) From b25bec53d89c2bac457d5d686ba6725007fd23d6 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 13 May 2020 18:42:56 +0200 Subject: [PATCH 038/118] refactor next_event --- Cargo.lock | 2 +- Cargo.toml | 2 +- deltachat-ffi/deltachat.h | 6 ------ deltachat-ffi/src/lib.rs | 12 ------------ examples/simple.rs | 8 ++------ src/context.rs | 18 ++++++------------ 6 files changed, 10 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ff9ad0b17..5e2509042 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -670,7 +670,7 @@ dependencies = [ "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "charset 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "debug_stub_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "deltachat_derive 2.0.0", "email 0.0.21 (git+https://github.com/deltachat/rust-email)", diff --git a/Cargo.toml b/Cargo.toml index 9d9e5577c..df3e5f387 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,7 +56,6 @@ encoded-words = { git = "https://github.com/async-email/encoded-words", branch=" native-tls = "0.2.3" image = { version = "0.22.4", default-features=false, features = ["gif_codec", "jpeg", "ico", "png_codec", "pnm", "webp", "bmp"] } futures = "0.3.4" -crossbeam-queue = "0.2.1" thiserror = "1.0.14" anyhow = "1.0.28" @@ -65,6 +64,7 @@ log = {version = "0.4.8", optional = true } rustyline = { version = "4.1.0", optional = true } ansi_term = { version = "0.12.1", optional = true } async-trait = "0.1.31" +crossbeam-channel = "0.4.2" [dev-dependencies] tempfile = "3.0" diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index e8a231386..7a497d369 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -190,12 +190,6 @@ typedef struct _dc_event dc_event_t; */ - -/** - * TODO: document - */ -int dc_has_next_event(dc_context_t* context); - /** * TODO: document */ diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index c5171c764..0c4949d30 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -501,18 +501,6 @@ pub unsafe extern "C" fn dc_is_running(context: *mut dc_context_t) -> libc::c_in with_inner_async!(ffi_context, ctx, { ctx.is_running() }).unwrap_or_default() as libc::c_int } -#[no_mangle] -pub unsafe extern "C" fn dc_has_next_event(context: *mut dc_context_t) -> libc::c_int { - if context.is_null() { - return 0; - } - let ffi_context = &*context; - - ffi_context - .with_inner(|ctx| ctx.has_next_event()) - .unwrap_or_default() as libc::c_int -} - pub struct EventWrapper { pub event_id: libc::c_int, pub data1: uintptr_t, diff --git a/examples/simple.rs b/examples/simple.rs index 354a9b7d3..2542c89cd 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -41,12 +41,8 @@ async fn main() { let ctx1 = ctx.clone(); async_std::task::spawn(async move { loop { - if ctx1.has_next_event() { - if let Ok(event) = ctx1.get_next_event() { - cb(event); - } - } else { - async_std::task::sleep(time::Duration::from_millis(50)).await; + if let Ok(event) = ctx1.get_next_event() { + cb(event); } } }); diff --git a/src/context.rs b/src/context.rs index 58b5d534b..5c554ba5f 100644 --- a/src/context.rs +++ b/src/context.rs @@ -7,7 +7,7 @@ use std::ops::Deref; use anyhow::anyhow; use async_std::path::{Path, PathBuf}; use async_std::sync::{channel, Arc, Mutex, Receiver, RwLock, Sender}; -use crossbeam_queue::SegQueue; +use crossbeam_channel::{unbounded, Receiver as SyncReceiver, Sender as SyncSender}; use crate::chat::*; use crate::config::Config; @@ -53,7 +53,7 @@ pub struct InnerContext { /// Mutex to avoid generating the key for the user more than once. pub(crate) generating_key_mutex: Mutex<()>, pub(crate) translated_stockstrings: RwLock>, - pub(crate) logs: SegQueue, + pub(crate) logs: (SyncSender, SyncReceiver), pub(crate) scheduler: RwLock, @@ -118,7 +118,7 @@ impl Context { last_smeared_timestamp: RwLock::new(0), generating_key_mutex: Mutex::new(()), translated_stockstrings: RwLock::new(HashMap::new()), - logs: SegQueue::new(), + logs: unbounded(), scheduler: RwLock::new(Scheduler::Stopped), creation_time: std::time::SystemTime::now(), }; @@ -170,22 +170,16 @@ impl Context { } pub fn call_cb(&self, event: Event) { - self.logs.push(event); + self.logs.0.send(event).expect("failed to send event"); } pub fn get_next_event(&self) -> Result { - let event = self.logs.pop().map_err(|err| anyhow!("{}", err))?; + let event = self.logs.1.recv().map_err(|err| anyhow!("{}", err))?; Ok(event) } - pub fn has_next_event(&self) -> bool { - !self.logs.is_empty() - } - - /******************************************************************************* - * Ongoing process allocation/free/check - ******************************************************************************/ + // Ongoing process allocation/free/check pub async fn alloc_ongoing(&self) -> Result> { if self.has_ongoing().await { From f7a7debd9d6b354a64a6a59e59f60a3eef3035c1 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Tue, 19 May 2020 12:18:47 +0200 Subject: [PATCH 039/118] update dependencies --- Cargo.lock | 215 ++++++++++++++++++++++++++++----------- Cargo.toml | 10 +- deltachat-ffi/Cargo.toml | 2 +- 3 files changed, 163 insertions(+), 64 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 367a0517c..839f7be4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -106,7 +106,7 @@ dependencies = [ [[package]] name = "async-imap" version = "0.2.0" -source = "git+https://github.com/async-email/async-imap?branch=feat/send#94a48158d6cc622aa0ed99d10b84088e9e53eb2f" +source = "git+https://github.com/async-email/async-imap?branch=feat/send#f55b9af2ca0ff99acbfc5ca30b24748501b5ef61" dependencies = [ "async-native-tls 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -137,7 +137,7 @@ dependencies = [ [[package]] name = "async-smtp" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "async-native-tls 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -154,33 +154,7 @@ dependencies = [ "serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)", - "snafu 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "async-std" -version = "1.5.0" -source = "git+https://github.com/async-rs/async-std?rev=3ff9e98f20a193eb63e43fb9d71f9d60c33f6d58#3ff9e98f20a193eb63e43fb9d71f9d60c33f6d58" -dependencies = [ - "async-attributes 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "async-task 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "broadcaster 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-timer 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "kv-log-macro 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.22 (registry+https://github.com/rust-lang/crates.io-index)", - "mio-uds 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)", - "once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project-lite 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -208,6 +182,30 @@ dependencies = [ "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "async-std" +version = "1.6.0-beta.1" +source = "git+https://github.com/async-rs/async-std#9e6a76af04a08d3e3d13f4366c5d254dc2c22b94" +dependencies = [ + "async-attributes 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "async-task 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-timer 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "kv-log-macro 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)", + "once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-project-lite 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "smol 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-futures 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "async-task" version = "1.3.1" @@ -217,6 +215,11 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "async-task" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "async-trait" version = "0.1.31" @@ -530,6 +533,19 @@ dependencies = [ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "crossbeam" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "crossbeam-channel" version = "0.4.2" @@ -661,8 +677,8 @@ dependencies = [ "anyhow 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)", "async-imap 0.2.0 (git+https://github.com/async-email/async-imap?branch=feat/send)", "async-native-tls 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "async-smtp 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "async-std 1.5.0 (git+https://github.com/async-rs/async-std?rev=3ff9e98f20a193eb63e43fb9d71f9d60c33f6d58)", + "async-smtp 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "async-std 1.6.0-beta.1 (git+https://github.com/async-rs/async-std)", "async-trait 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)", "backtrace 0.3.48 (registry+https://github.com/rust-lang/crates.io-index)", "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -697,11 +713,11 @@ dependencies = [ "proptest 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)", "quick-xml 0.17.2 (registry+https://github.com/rust-lang/crates.io-index)", "r2d2 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)", - "r2d2_sqlite 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", + "r2d2_sqlite 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "reqwest 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)", - "rusqlite 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rusqlite 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", "rustyline 4.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "sanitize-filename 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", @@ -729,7 +745,7 @@ name = "deltachat_ffi" version = "1.33.0" dependencies = [ "anyhow 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)", - "async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "async-std 1.6.0-beta.1 (git+https://github.com/async-rs/async-std)", "deltachat 1.33.0", "human-panic 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", @@ -794,11 +810,6 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "doc-comment" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "dtoa" version = "0.4.5" @@ -1111,6 +1122,15 @@ name = "futures-timer" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "futures-timer" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gloo-timers 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "send_wrapper 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "futures-util" version = "0.3.5" @@ -1167,6 +1187,18 @@ name = "glob" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "gloo-timers" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "js-sys 0.3.39 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "web-sys 0.3.39 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "h2" version = "0.2.5" @@ -1659,6 +1691,18 @@ dependencies = [ "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "nix" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "nodrop" version = "0.1.14" @@ -1937,6 +1981,15 @@ name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "piper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "pkg-config" version = "0.3.17" @@ -2067,11 +2120,11 @@ dependencies = [ [[package]] name = "r2d2_sqlite" -version = "0.13.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "r2d2 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)", - "rusqlite 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rusqlite 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2350,7 +2403,7 @@ dependencies = [ [[package]] name = "rusqlite" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2457,6 +2510,11 @@ dependencies = [ "parking_lot 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "scoped-tls-hkt" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "scopeguard" version = "1.1.0" @@ -2497,6 +2555,11 @@ name = "semver-parser" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "send_wrapper" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "serde" version = "1.0.110" @@ -2596,22 +2659,32 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "snafu" -version = "0.6.8" +name = "smol" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "doc-comment 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "snafu-derive 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", + "async-task 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)", + "once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "piper 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "scoped-tls-hkt 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "socket2 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)", + "wepoll-binding 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "snafu-derive" -version = "0.6.8" +name = "socket2" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.22 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -3067,6 +3140,23 @@ dependencies = [ "wasm-bindgen 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "wepoll-binding" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "wepoll-sys 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wepoll-sys" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "winapi" version = "0.2.8" @@ -3175,10 +3265,11 @@ dependencies = [ "checksum async-attributes 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "efd3d156917d94862e779f356c5acae312b08fd3121e792c857d7928c8088423" "checksum async-imap 0.2.0 (git+https://github.com/async-email/async-imap?branch=feat/send)" = "" "checksum async-native-tls 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9e9e7a929bd34c68a82d58a4de7f86fffdaf97fb2af850162a7bb19dd7269b33" -"checksum async-smtp 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3652e5c6072c0694a2bcdb7e8409980d2676bd4f024adf4aab10c68fd2b48f5" -"checksum async-std 1.5.0 (git+https://github.com/async-rs/async-std?rev=3ff9e98f20a193eb63e43fb9d71f9d60c33f6d58)" = "" +"checksum async-smtp 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb010dac8f81ceb798b089c522766c0427b54253789194b5c7de9720aeb7f091" "checksum async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "538ecb01eb64eecd772087e5b6f7540cbc917f047727339a472dafed2185b267" +"checksum async-std 1.6.0-beta.1 (git+https://github.com/async-rs/async-std)" = "" "checksum async-task 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0ac2c016b079e771204030951c366db398864f5026f84a44dafb0ff20f02085d" +"checksum async-task 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c17772156ef2829aadc587461c7753af20b7e8db1529bc66855add962a3b35d3" "checksum async-trait 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "26c4f3195085c36ea8d24d32b2f828d23296a9370a28aa39d111f6f16bef9f3b" "checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" "checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" @@ -3221,6 +3312,7 @@ dependencies = [ "checksum core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" "checksum crc24 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "fd121741cf3eb82c08dd3023eb55bf2665e5f60ec20f89760cf836ae4562e6a0" "checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" +"checksum crossbeam 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "69323bff1fb41c635347b8ead484a5ca6c3f11914d784170b158d8449ab07f8e" "checksum crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061" "checksum crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" "checksum crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" @@ -3239,7 +3331,6 @@ dependencies = [ "checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" "checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" "checksum dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" -"checksum doc-comment 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" "checksum dtoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3" "checksum ed25519-dalek 1.0.0-pre.3 (registry+https://github.com/rust-lang/crates.io-index)" = "978710b352437433c97b2bff193f2fb1dfd58a093f863dd95e225a19baa599a2" "checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" @@ -3279,12 +3370,14 @@ dependencies = [ "checksum futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc" "checksum futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626" "checksum futures-timer 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a1de7508b218029b0f01662ed8f61b1c964b3ae99d6f25462d0f55a595109df6" +"checksum futures-timer 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" "checksum futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6" "checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" "checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" "checksum gif 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)" = "471d90201b3b223f3451cd4ad53e34295f16a1df17b1edf3736d47761c3981af" "checksum gimli 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bcc8e0c9bce37868955864dbecd2b1ab2bdf967e6f28066d65aaac620444b65c" "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" +"checksum gloo-timers 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f" "checksum h2 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "79b7246d7e4b979c03fa093da39cfb3617a96bbeee6310af63991668d7e843ff" "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" "checksum hermit-abi 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71" @@ -3339,6 +3432,7 @@ dependencies = [ "checksum native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "2b0d88c06fe90d5ee94048ba40409ef1d9315d86f6f38c2efdaad4fb50c58b2d" "checksum net2 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)" = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7" "checksum nix 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4dbdc256eaac2e3bd236d93ad999d3479ef775c863dbda3068c4006a92eec51b" +"checksum nix 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" "checksum nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" "checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" "checksum nom 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b471253da97532da4b61552249c521e01e736071f71c1a4f7ebbfbf0a06aad6" @@ -3367,6 +3461,7 @@ dependencies = [ "checksum pin-project-internal 0.4.17 (registry+https://github.com/rust-lang/crates.io-index)" = "e58db2081ba5b4c93bd6be09c40fd36cb9193a8336c384f3b40012e531aa7e40" "checksum pin-project-lite 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f7505eeebd78492e0f6108f7171c4948dbb120ee8119d9d77d0afa5469bef67f" "checksum pin-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +"checksum piper 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c6d62a6ea407d82215154475927b288219b79c8670e3371166210328e758ebaa" "checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" "checksum png 0.15.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ef859a23054bbfee7811284275ae522f0434a3c8e7f4b74bd4a35ae7e1c4a283" "checksum ppv-lite86 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" @@ -3383,7 +3478,7 @@ dependencies = [ "checksum quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea" "checksum quoted_printable 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "47b080c5db639b292ac79cbd34be0cfc5d36694768d8341109634d90b86930e2" "checksum r2d2 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1497e40855348e4a8a40767d8e55174bce1e445a3ac9254ad44ad468ee0485af" -"checksum r2d2_sqlite 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b061f5b16692bbe81eeb260f92e6fc7d13aea455c4cbe67f5c4aa20aa92d1d9e" +"checksum r2d2_sqlite 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5e15ff794e7c8bb8ae20ccac5bac6a93a4a3af708dd801d4094f80da41196f33" "checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" "checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" "checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" @@ -3410,7 +3505,7 @@ dependencies = [ "checksum reqwest 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)" = "02b81e49ddec5109a9dcfc5f2a317ff53377c915e9ae9d4f2fb50914b85614e2" "checksum ripemd160 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ad5112e0dbbb87577bfbc56c42450235e3012ce336e29c5befd7807bd626da4a" "checksum rsa 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6ed8692d8e0ea3baae03f0f32ecfc13a6c6f1f85fcd6d9fdefcdf364e70f4df9" -"checksum rusqlite 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)" = "64a656821bb6317a84b257737b7934f79c0dbb7eb694710475908280ebad3e64" +"checksum rusqlite 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)" = "57edf4c4cea4d7e0fab069acb5da9e8e8e5403c78abc81b1f37d83af02148ea5" "checksum rust-argon2 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017" "checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" @@ -3422,11 +3517,13 @@ dependencies = [ "checksum sanitize-filename 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "23fd0fec94ec480abfd86bb8f4f6c57e0efb36dac5c852add176ea7b04c74801" "checksum schannel 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)" = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" "checksum scheduled-thread-pool 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0988d7fdf88d5e5fcf5923a0f1e8ab345f3e98ab4bc6bc45a2d5ff7f7458fbf6" +"checksum scoped-tls-hkt 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c2e9d7eaddb227e8fbaaa71136ae0e1e913ca159b86c7da82f3e8f0044ad3a63" "checksum scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" "checksum security-framework 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "64808902d7d99f78eaddd2b4e2509713babc3dc3c85ad6f4c447680f3c01e535" "checksum security-framework-sys 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "17bf11d99252f512695eb468de5516e5cf75455521e69dfe343f3b74e4748405" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +"checksum send_wrapper 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" "checksum serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)" = "99e7b308464d16b56eba9964e4972a3eee817760ab60d88c3f86e1fecb08204c" "checksum serde_derive 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)" = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984" "checksum serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)" = "993948e75b189211a9b31a7528f950c6adc21f9720b6438ff80a7fa2f864cea2" @@ -3437,8 +3534,8 @@ dependencies = [ "checksum skeptic 0.13.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6fb8ed853fdc19ce09752d63f3a2e5b5158aeb261520cd75eb618bd60305165" "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" "checksum smallvec 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" -"checksum snafu 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "c7f5aed652511f5c9123cf2afbe9c244c29db6effa2abb05c866e965c82405ce" -"checksum snafu-derive 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ebf8f7d5720104a9df0f7076a8682024e958bba0fe9848767bb44f251f3648e9" +"checksum smol 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7b1014c048f35c553bfd0155079a62ab9a0e220f9e39f49244c1d58f825ab84a" +"checksum socket2 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)" = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918" "checksum spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" "checksum static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3" @@ -3496,6 +3593,8 @@ dependencies = [ "checksum wasm-bindgen-macro-support 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "8eb197bd3a47553334907ffd2f16507b4f4f01bbec3ac921a7719e0decdfe72a" "checksum wasm-bindgen-shared 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "a91c2916119c17a8e316507afaaa2dd94b47646048014bbdf6bef098c1bb58ad" "checksum web-sys 0.3.39 (registry+https://github.com/rust-lang/crates.io-index)" = "8bc359e5dd3b46cb9687a051d50a2fdd228e4ba7cf6fcf861a5365c3d671a642" +"checksum wepoll-binding 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7511014d92edaa2cba607fd6dc62974915bebc3e16f91f911a4bb240b4a3a68c" +"checksum wepoll-sys 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9082a777aed991f6769e2b654aa0cb29f1c3d615daf009829b07b66c7aff6a24" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" diff --git a/Cargo.toml b/Cargo.toml index 91f23517b..411d7b818 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,12 +20,12 @@ smallvec = "1.0.0" reqwest = { version = "0.10.0", features = ["blocking", "json"] } num-derive = "0.3.0" num-traits = "0.2.6" -async-smtp = "0.2" +async-smtp = "0.3" email = { git = "https://github.com/deltachat/rust-email", branch = "master" } lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" } async-imap = { git = "https://github.com/async-email/async-imap", branch = "feat/send" } async-native-tls = "0.3.1" -async-std = { git = "https://github.com/async-rs/async-std", rev = "3ff9e98f20a193eb63e43fb9d71f9d60c33f6d58", features = ["unstable"] } +async-std = { git = "https://github.com/async-rs/async-std", features = ["unstable"] } base64 = "0.11" charset = "0.1" percent-encoding = "2.0" @@ -35,8 +35,8 @@ chrono = "0.4.6" indexmap = "1.3.0" lazy_static = "1.4.0" regex = "1.1.6" -rusqlite = { version = "0.21", features = ["bundled"] } -r2d2_sqlite = "0.13.0" +rusqlite = { version = "0.22", features = ["bundled"] } +r2d2_sqlite = "0.15.0" r2d2 = "0.8.5" strum = "0.16.0" strum_macros = "0.16.0" @@ -71,7 +71,7 @@ tempfile = "3.0" pretty_assertions = "0.6.1" pretty_env_logger = "0.3.0" proptest = "0.9.4" -async-std = { git = "https://github.com/async-rs/async-std", rev = "3ff9e98f20a193eb63e43fb9d71f9d60c33f6d58", features = ["unstable", "attributes"] } +async-std = { git = "https://github.com/async-rs/async-std", features = ["unstable", "attributes"] } [workspace] members = [ diff --git a/deltachat-ffi/Cargo.toml b/deltachat-ffi/Cargo.toml index 887fcff78..f7bbcb436 100644 --- a/deltachat-ffi/Cargo.toml +++ b/deltachat-ffi/Cargo.toml @@ -20,7 +20,7 @@ libc = "0.2" human-panic = "1.0.1" num-traits = "0.2.6" serde_json = "1.0" -async-std = "1.5.0" +async-std = { git = "https://github.com/async-rs/async-std" } anyhow = "1.0.28" thiserror = "1.0.14" From f81c1afde774de29c8c7a353182126900eda228c Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Tue, 19 May 2020 13:30:15 +0200 Subject: [PATCH 040/118] bring back busy_timeout --- src/sql.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/sql.rs b/src/sql.rs index 7a772659b..47c2950ac 100644 --- a/src/sql.rs +++ b/src/sql.rs @@ -5,6 +5,7 @@ use async_std::sync::{Arc, RwLock}; use std::collections::HashSet; use std::path::Path; +use std::time::Duration; use rusqlite::{Connection, Error as SqlError, OpenFlags}; use thread_local_object::ThreadLocal; @@ -709,13 +710,16 @@ async fn open( let mgr = r2d2_sqlite::SqliteConnectionManager::file(dbfile.as_ref()) .with_flags(open_flags) .with_init(|c| { - c.execute_batch("PRAGMA secure_delete=on;")?; + c.execute_batch(&format!( + "PRAGMA secure_delete=on; PRAGMA busy_timeout = {};", + Duration::from_secs(10).as_millis() + ))?; Ok(()) }); let pool = r2d2::Pool::builder() .min_idle(Some(2)) .max_size(10) - .connection_timeout(std::time::Duration::new(60, 0)) + .connection_timeout(Duration::from_secs(60)) .build(mgr) .map_err(Error::ConnectionPool)?; From 2cfd5754ca2f3b4f7f4209adf2d6b5b184e51466 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Tue, 19 May 2020 14:29:47 +0200 Subject: [PATCH 041/118] python: start integration of get_next_event --- python/src/deltachat/__init__.py | 48 ------------------------ python/src/deltachat/account.py | 48 ++++++++++++++++-------- python/src/deltachat/eventlogger.py | 18 +++------ python/src/deltachat/iothreads.py | 57 +++-------------------------- python/tests/test_lowlevel.py | 9 +---- 5 files changed, 45 insertions(+), 135 deletions(-) diff --git a/python/src/deltachat/__init__.py b/python/src/deltachat/__init__.py index 3d7e43e61..fbd20283d 100644 --- a/python/src/deltachat/__init__.py +++ b/python/src/deltachat/__init__.py @@ -17,54 +17,6 @@ except DistributionNotFound: __version__ = "0.0.0.dev0-unknown" -_DC_CALLBACK_MAP = {} - - -def py_dc_callback(ctx, evt, data1, data2): - """The global event handler. - - CFFI only allows us to set one global event handler, so this one - looks up the correct event handler for the given context. - """ - try: - callback = _DC_CALLBACK_MAP.get(ctx, lambda *a: 0) - except AttributeError: - # we are in a deep in GC-free/interpreter shutdown land - # nothing much better to do here than: - return 0 - - # the following code relates to the deltachat/_build.py's helper - # function which provides us signature info of an event call - evt_name = get_dc_event_name(evt) - event_sig_types = capi.lib.dc_get_event_signature_types(evt) - if data1 and event_sig_types & 1: - data1 = ffi.string(ffi.gc(ffi.cast('char*', data1), capi.lib.dc_str_unref)).decode("utf8") - if data2 and event_sig_types & 2: - data2 = ffi.string(ffi.gc(ffi.cast('char*', data2), capi.lib.dc_str_unref)).decode("utf8") - try: - if isinstance(data2, bytes): - data2 = data2.decode("utf8") - except UnicodeDecodeError: - # XXX ignoring the decode error is not quite correct but for now - # i don't want to hunt down encoding problems in the c lib - pass - try: - callback(ctx, evt_name, data1, data2) - except: # noqa - raise - - -def set_context_callback(dc_context, func): - _DC_CALLBACK_MAP[dc_context] = func - - -def clear_context_callback(dc_context): - try: - _DC_CALLBACK_MAP.pop(dc_context, None) - except AttributeError: - pass - - def get_dc_event_name(integer, _DC_EVENTNAME_MAP={}): if not _DC_EVENTNAME_MAP: for name, val in vars(const).items(): diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index c641a8e8f..03c9f12d7 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -17,6 +17,7 @@ from .message import Message, map_system_message from .contact import Contact from .tracker import ImexTracker from . import hookspec, iothreads +from .eventlogger import FFIEvent class MissingCredentials(ValueError): @@ -53,7 +54,7 @@ class Account(object): self._hook_event_queue = queue.Queue() self._in_use_iter_events = False self._shutdown_event = Event() - + # open database self.db_path = db_path if hasattr(db_path, "encode"): @@ -63,6 +64,7 @@ class Account(object): self._configkeys = self.get_config("sys.config_keys").split() atexit.register(self.shutdown) hook.dc_account_init(account=self) + self._threads.start() def disable_logging(self): """ disable logging. """ @@ -75,8 +77,7 @@ class Account(object): @hookspec.account_hookimpl def ac_process_ffi_event(self, ffi_event): for name, kwargs in self._map_ffi_event(ffi_event): - ev = HookEvent(self, name=name, kwargs=kwargs) - self._hook_event_queue.put(ev) + yield HookEvent(self, name=name, kwargs=kwargs) # def __del__(self): # self.shutdown() @@ -562,7 +563,7 @@ class Account(object): """ Stop ongoing securejoin, configuration or other core jobs. """ lib.dc_stop_ongoing_process(self._dc_context) - def start(self, callback_thread=True): + def start(self): """ start this account (activate imap/smtp threads etc.) and return immediately. @@ -580,7 +581,6 @@ class Account(object): if not self.get_config("addr") or not self.get_config("mail_pw"): raise MissingCredentials("addr or mail_pwd not set in config") lib.dc_configure(self._dc_context) - self._threads.start(callback_thread=callback_thread) def wait_shutdown(self): """ wait until shutdown of this account has completed. """ @@ -588,7 +588,7 @@ class Account(object): def shutdown(self, wait=True): """ shutdown account, stop threads and close and remove - underlying dc_context and callbacks. """ + underlying dc_context.""" dc_context = self._dc_context if dc_context is None: return @@ -624,10 +624,34 @@ class Account(object): raise RuntimeError("can only call iter_events() from one thread") self._in_use_iter_events = True while 1: - event = self._hook_event_queue.get(timeout=timeout) - if event is None: + event = lib.dc_get_next_event(self._dc_context) + if event == ffi.NULL: break - yield event + + ctx = self._dc_context + evt = lib.dc_event_get_id(event) + data1 = lib.dc_event_get_data1(event) + data2 = lib.dc_event_get_data2(event) + # the following code relates to the deltachat/_build.py's helper + # function which provides us signature info of an event call + evt_name = deltachat.get_dc_event_name(evt) + event_sig_types = lib.dc_get_event_signature_types(evt) + if data1 and event_sig_types & 1: + data1 = ffi.string(ffi.gc(ffi.cast('char*', data1), lib.dc_str_unref)).decode("utf8") + if data2 and event_sig_types & 2: + data2 = ffi.string(ffi.gc(ffi.cast('char*', data2), lib.dc_str_unref)).decode("utf8") + try: + if isinstance(data2, bytes): + data2 = data2.decode("utf8") + except UnicodeDecodeError: + # XXX ignoring the decode error is not quite correct but for now + # i don't want to hunt down encoding problems in the c lib + pass + + lib.dc_event_unref(event) + ffi_event = FFIEvent(name=evt_name, data1=data1, data2=data2) + for event in self._pm.hook.ac_process_ffi_event(account=self, ffi_event=ffi_event): + yield event def _map_ffi_event(self, ffi_event): name = ffi_event.name @@ -660,12 +684,6 @@ class Account(object): def _destroy_dc_context(dc_context, dc_context_unref=lib.dc_context_unref): # destructor for dc_context dc_context_unref(dc_context) - try: - deltachat.clear_context_callback(dc_context) - except (TypeError, AttributeError): - # we are deep into Python Interpreter shutdown, - # so no need to clear the callback context mapping. - pass class ScannedQRCode: diff --git a/python/src/deltachat/eventlogger.py b/python/src/deltachat/eventlogger.py index 5490e7e72..e225843e6 100644 --- a/python/src/deltachat/eventlogger.py +++ b/python/src/deltachat/eventlogger.py @@ -6,21 +6,13 @@ from queue import Queue, Empty from .hookspec import account_hookimpl, global_hookimpl -@global_hookimpl -def dc_account_init(account): - # send all FFI events for this account to a plugin hook - def _ll_event(ctx, evt_name, data1, data2): - assert ctx == account._dc_context - ffi_event = FFIEvent(name=evt_name, data1=data1, data2=data2) - account._pm.hook.ac_process_ffi_event( - account=account, ffi_event=ffi_event - ) - deltachat.set_context_callback(account._dc_context, _ll_event) +# @global_hookimpl +# def dc_account_init(account): + # account._threads.start() +# @global_hookimpl +# def dc_account_after_shutdown(dc_context): -@global_hookimpl -def dc_account_after_shutdown(dc_context): - deltachat.clear_context_callback(dc_context) class FFIEvent: diff --git a/python/src/deltachat/iothreads.py b/python/src/deltachat/iothreads.py index 1e9a864f1..901b6af7a 100644 --- a/python/src/deltachat/iothreads.py +++ b/python/src/deltachat/iothreads.py @@ -4,8 +4,9 @@ import time from contextlib import contextmanager -from .capi import lib - +from .capi import ffi, lib +import deltachat +from .eventlogger import FFIEvent class IOThreads: def __init__(self, account): @@ -17,19 +18,10 @@ class IOThreads: def is_started(self): return len(self._name2thread) > 0 - def start(self, callback_thread): + def start(self): assert not self.is_started() - self._start_one_thread("inbox", self.imap_thread_run) - self._start_one_thread("smtp", self.smtp_thread_run) - if callback_thread: - self._start_one_thread("cb", self.cb_thread_run) - - if int(self.account.get_config("mvbox_watch")): - self._start_one_thread("mvbox", self.mvbox_thread_run) - - if int(self.account.get_config("sentbox_watch")): - self._start_one_thread("sentbox", self.sentbox_thread_run) + self._start_one_thread("cb", self.cb_thread_run) def _start_one_thread(self, name, func): self._name2thread[name] = t = threading.Thread(target=func, name=name) @@ -49,12 +41,6 @@ class IOThreads: # not in between checking for quitflag and entering idle. time.sleep(0.5) - lib.dc_interrupt_imap_idle(self._dc_context) - lib.dc_interrupt_smtp_idle(self._dc_context) - if "mvbox" in self._name2thread: - lib.dc_interrupt_mvbox_idle(self._dc_context) - if "sentbox" in self._name2thread: - lib.dc_interrupt_sentbox_idle(self._dc_context) if wait: for name, thread in self._name2thread.items(): if thread != threading.currentThread(): @@ -68,39 +54,8 @@ class IOThreads: ev = next(it) except StopIteration: break + print("{}", ev) self.account.ac_log_line("calling hook name={} kwargs={}".format(ev.name, ev.kwargs)) ev.call_hook() - def imap_thread_run(self): - with self.log_execution("INBOX THREAD START"): - while not self._thread_quitflag: - lib.dc_perform_imap_jobs(self._dc_context) - if not self._thread_quitflag: - lib.dc_perform_imap_fetch(self._dc_context) - if not self._thread_quitflag: - lib.dc_perform_imap_idle(self._dc_context) - def mvbox_thread_run(self): - with self.log_execution("MVBOX THREAD"): - while not self._thread_quitflag: - lib.dc_perform_mvbox_jobs(self._dc_context) - if not self._thread_quitflag: - lib.dc_perform_mvbox_fetch(self._dc_context) - if not self._thread_quitflag: - lib.dc_perform_mvbox_idle(self._dc_context) - - def sentbox_thread_run(self): - with self.log_execution("SENTBOX THREAD"): - while not self._thread_quitflag: - lib.dc_perform_sentbox_jobs(self._dc_context) - if not self._thread_quitflag: - lib.dc_perform_sentbox_fetch(self._dc_context) - if not self._thread_quitflag: - lib.dc_perform_sentbox_idle(self._dc_context) - - def smtp_thread_run(self): - with self.log_execution("SMTP THREAD"): - while not self._thread_quitflag: - lib.dc_perform_smtp_jobs(self._dc_context) - if not self._thread_quitflag: - lib.dc_perform_smtp_idle(self._dc_context) diff --git a/python/tests/test_lowlevel.py b/python/tests/test_lowlevel.py index 6c62afb0f..9161f4dc1 100644 --- a/python/tests/test_lowlevel.py +++ b/python/tests/test_lowlevel.py @@ -2,7 +2,7 @@ from __future__ import print_function import threading import time -from deltachat import capi, cutil, const, set_context_callback, clear_context_callback +from deltachat import capi, cutil, const from deltachat import register_global_plugin from deltachat.hookspec import global_hookimpl from deltachat.capi import ffi @@ -43,13 +43,6 @@ def test_empty_context(): capi.lib.dc_close(ctx) -def test_callback_None2int(): - ctx = capi.lib.dc_context_new(ffi.NULL, ffi.NULL) - set_context_callback(ctx, lambda *args: None) - capi.lib.dc_close(ctx) - clear_context_callback(ctx) - - def test_dc_close_events(tmpdir, acfactory): ac1 = acfactory.get_unconfigured_account() From cfd68f9f2e942d929cf5abdc29fcea72cd64b48b Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Tue, 19 May 2020 14:40:59 +0200 Subject: [PATCH 042/118] python: remove unused queue --- python/src/deltachat/account.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index 03c9f12d7..bc850017d 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -51,7 +51,6 @@ class Account(object): hook = hookspec.Global._get_plugin_manager().hook self._threads = iothreads.IOThreads(self) - self._hook_event_queue = queue.Queue() self._in_use_iter_events = False self._shutdown_event = Event() @@ -597,7 +596,6 @@ class Account(object): self.stop_ongoing() self._threads.stop(wait=False) lib.dc_close(dc_context) - self._hook_event_queue.put(None) self._threads.stop(wait=wait) # to wait for threads self._dc_context = None atexit.unregister(self.shutdown) @@ -605,16 +603,6 @@ class Account(object): hook = hookspec.Global._get_plugin_manager().hook hook.dc_account_after_shutdown(account=self, dc_context=dc_context) - def _handle_current_events(self): - """ handle all currently queued events and then return. """ - while 1: - try: - event = self._hook_event_queue.get(block=False) - except queue.Empty: - break - else: - event.call_hook() - def iter_events(self, timeout=None): """ yield hook events until shutdown. From a6409dcd27840a3e1b4e98ebd75e100e9c5a2dd9 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Tue, 19 May 2020 15:02:05 +0200 Subject: [PATCH 043/118] attempt to fix cb/python event handling --- python/src/deltachat/account.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index bc850017d..915ef0dae 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -53,7 +53,7 @@ class Account(object): self._threads = iothreads.IOThreads(self) self._in_use_iter_events = False self._shutdown_event = Event() - + # open database self.db_path = db_path if hasattr(db_path, "encode"): @@ -73,11 +73,6 @@ class Account(object): """ re-enable logging. """ self._logging = True - @hookspec.account_hookimpl - def ac_process_ffi_event(self, ffi_event): - for name, kwargs in self._map_ffi_event(ffi_event): - yield HookEvent(self, name=name, kwargs=kwargs) - # def __del__(self): # self.shutdown() @@ -616,7 +611,6 @@ class Account(object): if event == ffi.NULL: break - ctx = self._dc_context evt = lib.dc_event_get_id(event) data1 = lib.dc_event_get_data1(event) data2 = lib.dc_event_get_data2(event) @@ -638,8 +632,9 @@ class Account(object): lib.dc_event_unref(event) ffi_event = FFIEvent(name=evt_name, data1=data1, data2=data2) - for event in self._pm.hook.ac_process_ffi_event(account=self, ffi_event=ffi_event): - yield event + self._pm.hook.ac_process_ffi_event(account=self, ffi_event=ffi_event) + for name, kwargs in self._map_ffi_event(ffi_event): + yield HookEvent(self, name=name, kwargs=kwargs) def _map_ffi_event(self, ffi_event): name = ffi_event.name From 236e9562fdb54d61177672770703629679d8230c Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Tue, 19 May 2020 15:31:28 +0200 Subject: [PATCH 044/118] python: run and shutdown --- deltachat-ffi/src/lib.rs | 2 -- python/src/deltachat/account.py | 2 ++ python/src/deltachat/iothreads.py | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 0c4949d30..7dfa6b6bb 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -654,9 +654,7 @@ pub unsafe extern "C" fn dc_context_shutdown(context: *mut dc_context_t) { let ffi_context = &*context; with_inner_async!(ffi_context, ctx, async move { - eprintln!("SHUTDOWN"); ctx.stop().await; - eprintln!("SHUTDOWN:DONE") }) .unwrap_or(()) } diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index 915ef0dae..51003ac2e 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -575,6 +575,7 @@ class Account(object): if not self.get_config("addr") or not self.get_config("mail_pw"): raise MissingCredentials("addr or mail_pwd not set in config") lib.dc_configure(self._dc_context) + lib.dc_context_run(self._dc_context) def wait_shutdown(self): """ wait until shutdown of this account has completed. """ @@ -590,6 +591,7 @@ class Account(object): if self._threads.is_started(): self.stop_ongoing() self._threads.stop(wait=False) + lib.dc_context_shutdown(dc_context) lib.dc_close(dc_context) self._threads.stop(wait=wait) # to wait for threads self._dc_context = None diff --git a/python/src/deltachat/iothreads.py b/python/src/deltachat/iothreads.py index 901b6af7a..f4cc88faf 100644 --- a/python/src/deltachat/iothreads.py +++ b/python/src/deltachat/iothreads.py @@ -54,7 +54,6 @@ class IOThreads: ev = next(it) except StopIteration: break - print("{}", ev) self.account.ac_log_line("calling hook name={} kwargs={}".format(ev.name, ev.kwargs)) ev.call_hook() From 14c9161268720fe5adfbcc4846ab0ea64e2c8417 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Tue, 19 May 2020 15:45:08 +0200 Subject: [PATCH 045/118] python: fix basic start and stop --- python/src/deltachat/account.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index 51003ac2e..8c9643ffe 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -19,7 +19,6 @@ from .tracker import ImexTracker from . import hookspec, iothreads from .eventlogger import FFIEvent - class MissingCredentials(ValueError): """ Account is missing `addr` and `mail_pw` config values. """ @@ -63,7 +62,6 @@ class Account(object): self._configkeys = self.get_config("sys.config_keys").split() atexit.register(self.shutdown) hook.dc_account_init(account=self) - self._threads.start() def disable_logging(self): """ disable logging. """ @@ -576,6 +574,7 @@ class Account(object): raise MissingCredentials("addr or mail_pwd not set in config") lib.dc_configure(self._dc_context) lib.dc_context_run(self._dc_context) + self._threads.start() def wait_shutdown(self): """ wait until shutdown of this account has completed. """ @@ -591,6 +590,7 @@ class Account(object): if self._threads.is_started(): self.stop_ongoing() self._threads.stop(wait=False) + lib.dc_context_shutdown(dc_context) lib.dc_close(dc_context) self._threads.stop(wait=wait) # to wait for threads @@ -608,7 +608,7 @@ class Account(object): if self._in_use_iter_events: raise RuntimeError("can only call iter_events() from one thread") self._in_use_iter_events = True - while 1: + while lib.dc_is_open(self._dc_context): event = lib.dc_get_next_event(self._dc_context) if event == ffi.NULL: break From ba4df23bff19592ccd883f6c31bb809edb202689 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Tue, 19 May 2020 15:47:20 +0200 Subject: [PATCH 046/118] python: remove unused start_threads calls --- python/tests/test_account.py | 19 +------------------ python/tests/test_increation.py | 6 ------ 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/python/tests/test_account.py b/python/tests/test_account.py index 17e5b725c..6034534af 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -531,9 +531,7 @@ class TestOnlineAccount: # rsa key gen can be slow especially on CI, adjust timeout ac1._evtracker.set_timeout(120) wait_configuration_progress(ac1, 1000) - ac1.start_threads() wait_configuration_progress(ac2, 1000) - ac2.start_threads() chat = self.get_chat(ac1, ac2, both_created=True) lp.sec("ac1: send unencrypted message to ac2") @@ -588,11 +586,8 @@ class TestOnlineAccount: ac1_clone = acfactory.clone_online_account(ac1) wait_configuration_progress(ac1, 1000) - ac1.start_threads() wait_configuration_progress(ac2, 1000) - ac2.start_threads() wait_configuration_progress(ac1_clone, 1000) - ac1_clone.start_threads() chat = self.get_chat(ac1, ac2) @@ -697,11 +692,9 @@ class TestOnlineAccount: lp.sec("ac2: waiting for configuration") wait_configuration_progress(ac2, 1000) - ac2.start_threads() lp.sec("ac1: waiting for configuration") wait_configuration_progress(ac1, 1000) - ac1.start_threads() lp.sec("ac1: send message and wait for ac2 to receive it") chat = self.get_chat(ac1, ac2) @@ -714,9 +707,7 @@ class TestOnlineAccount: ac1 = acfactory.get_online_configuring_account() ac2 = acfactory.get_online_configuring_account(mvbox=True, move=True) wait_configuration_progress(ac2, 1000) - ac2.start_threads() wait_configuration_progress(ac1, 1000) - ac1.start_threads() chat = self.get_chat(ac1, ac2) chat.send_text("message1") ev = ac2._evtracker.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED") @@ -728,9 +719,7 @@ class TestOnlineAccount: ac1.set_config("bcc_self", "1") ac2 = acfactory.get_online_configuring_account() wait_configuration_progress(ac2, 1000) - ac2.start_threads() wait_configuration_progress(ac1, 1000) - ac1.start_threads() chat = self.get_chat(ac1, ac2) chat.send_text("message1") chat.send_text("message2") @@ -1110,7 +1099,6 @@ class TestOnlineAccount: def test_import_export_online_all(self, acfactory, tmpdir, lp): ac1 = acfactory.get_online_configuring_account() wait_configuration_progress(ac1, 1000) - ac1.start_threads() lp.sec("create some chat content") contact1 = ac1.create_contact("some1@hello.com", name="some1") @@ -1159,9 +1147,8 @@ class TestOnlineAccount: ac1 = acfactory.get_online_configuring_account() ac2 = acfactory.clone_online_account(ac1) wait_configuration_progress(ac2, 1000) - ac2.start_threads() wait_configuration_progress(ac1, 1000) - ac1.start_threads() + lp.sec("trigger ac setup message and return setupcode") assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"] setup_code = ac1.initiate_key_transfer() @@ -1184,9 +1171,7 @@ class TestOnlineAccount: ac2 = acfactory.clone_online_account(ac1) ac2._evtracker.set_timeout(30) wait_configuration_progress(ac2, 1000) - ac2.start_threads() wait_configuration_progress(ac1, 1000) - ac1.start_threads() lp.sec("trigger ac setup message but ignore") assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"] @@ -1497,7 +1482,6 @@ class TestGroupStressTests: accounts = [acfactory.get_online_configuring_account() for i in range(5)] for acc in accounts: wait_configuration_progress(acc, 1000) - acc.start_threads() ac1 = accounts.pop() lp.sec("ac1: setting up contacts with 4 other members") @@ -1602,7 +1586,6 @@ class TestGroupStressTests: accounts = [acfactory.get_online_configuring_account() for i in range(3)] for acc in accounts: wait_configuration_progress(acc, 1000) - acc.start_threads() ac1 = accounts.pop() lp.sec("ac1: setting up contacts with 2 other members") diff --git a/python/tests/test_increation.py b/python/tests/test_increation.py index f480ea289..6fb96e1fe 100644 --- a/python/tests/test_increation.py +++ b/python/tests/test_increation.py @@ -23,9 +23,7 @@ class TestOnlineInCreation: ac1 = acfactory.get_online_configuring_account() ac2 = acfactory.get_online_configuring_account() wait_configuration_progress(ac1, 1000) - ac1.start_threads() wait_configuration_progress(ac2, 1000) - ac2.start_threads() c2 = ac1.create_contact(email=ac2.get_config("addr")) chat = ac1.create_chat_by_contact(c2) @@ -40,9 +38,7 @@ class TestOnlineInCreation: ac1 = acfactory.get_online_configuring_account() ac2 = acfactory.get_online_configuring_account() wait_configuration_progress(ac1, 1000) - ac1.start_threads() wait_configuration_progress(ac2, 1000) - ac2.start_threads() c2 = ac1.create_contact(email=ac2.get_config("addr")) chat = ac1.create_chat_by_contact(c2) @@ -60,9 +56,7 @@ class TestOnlineInCreation: ac1 = acfactory.get_online_configuring_account() ac2 = acfactory.get_online_configuring_account() wait_configuration_progress(ac1, 1000) - ac1.start_threads() wait_configuration_progress(ac2, 1000) - ac2.start_threads() c2 = ac1.create_contact(email=ac2.get_config("addr")) chat = ac1.create_chat_by_contact(c2) From 133ff4914df14d3ba6e377b1e87c65357ca4da0e Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Tue, 19 May 2020 17:26:22 +0200 Subject: [PATCH 047/118] first pass at resolving CR --- src/chat.rs | 38 -------------------------------------- src/configure/mod.rs | 4 ++-- src/context.rs | 3 +++ src/imap/mod.rs | 8 ++++++++ src/imap/select_folder.rs | 22 ++-------------------- src/job.rs | 39 --------------------------------------- src/oauth2.rs | 8 +++----- 7 files changed, 18 insertions(+), 104 deletions(-) diff --git a/src/chat.rs b/src/chat.rs index a42c932c5..15f77375c 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -635,44 +635,6 @@ impl Chat { &self.name } - fn parent_query(fields: &str) -> String { - // Check for server_uid guarantees that we don't - // select a draft or undelivered message. - format!( - "SELECT {} \ - FROM msgs WHERE chat_id=?1 AND server_uid!=0 \ - ORDER BY timestamp DESC, id DESC \ - LIMIT 1;", - fields - ) - } - - async fn get_parent_mime_headers(&self, context: &Context) -> Option<(String, String, String)> { - let collect = |row: &rusqlite::Row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)); - let params = paramsv![self.id]; - let sql = &context.sql; - - let query = Self::parent_query("rfc724_mid, mime_in_reply_to, mime_references"); - - sql.query_row(&query, params, collect).await.ok() - } - - async fn parent_is_encrypted(&self, context: &Context) -> Result { - let sql = &context.sql; - let params = paramsv![self.id]; - let query = Self::parent_query("param"); - - let packed: Option = sql.query_get_value_result(&query, params).await?; - - if let Some(ref packed) = packed { - let param = packed.parse::()?; - Ok(param.exists(Param::GuaranteeE2ee)) - } else { - // No messages - Ok(false) - } - } - pub async fn get_profile_image(&self, context: &Context) -> Option { if let Some(image_rel) = self.param.get(Param::ProfileImage) { if !image_rel.is_empty() { diff --git a/src/configure/mod.rs b/src/configure/mod.rs index 65ce5a83c..96b21fb9d 100644 --- a/src/configure/mod.rs +++ b/src/configure/mod.rs @@ -44,11 +44,11 @@ impl Context { ensure!( !self.scheduler.read().await.is_running(), - "Can not configure, already running" + "cannot configure, already running" ); ensure!( self.sql.is_open().await, - "Cannot configure, database not opened." + "cannot configure, database not opened." ); let cancel_channel = self.alloc_ongoing().await?; diff --git a/src/context.rs b/src/context.rs index 414cd8069..edd436fa5 100644 --- a/src/context.rs +++ b/src/context.rs @@ -52,6 +52,8 @@ pub struct InnerContext { pub(crate) running_state: RwLock, /// Mutex to avoid generating the key for the user more than once. pub(crate) generating_key_mutex: Mutex<()>, + /// Mutex to enforce only a single running oauth2 is running. + pub(crate) oauth2_mutex: Mutex<()>, pub(crate) translated_stockstrings: RwLock>, pub(crate) logs: (SyncSender, SyncReceiver), @@ -117,6 +119,7 @@ impl Context { bob: RwLock::new(Default::default()), last_smeared_timestamp: RwLock::new(0), generating_key_mutex: Mutex::new(()), + oauth2_mutex: Mutex::new(()), translated_stockstrings: RwLock::new(HashMap::new()), logs: unbounded(), scheduler: RwLock::new(Scheduler::Stopped), diff --git a/src/imap/mod.rs b/src/imap/mod.rs index b53f3cb3b..794e01d8c 100644 --- a/src/imap/mod.rs +++ b/src/imap/mod.rs @@ -1227,6 +1227,14 @@ impl Imap { return; } + if !self + .add_flag_finalized_with_set(context, SELECT_ALL, "\\Deleted") + .await + { + error!(context, "Cannot mark messages for deletion {}", folder); + return; + } + // we now trigger expunge to actually delete messages self.config.selected_folder_needs_expunge = true; match self.select_folder::(context, None).await { diff --git a/src/imap/select_folder.rs b/src/imap/select_folder.rs index 819f11db8..35e2b4eb4 100644 --- a/src/imap/select_folder.rs +++ b/src/imap/select_folder.rs @@ -47,6 +47,7 @@ impl Imap { } self.config.selected_folder = None; self.config.selected_folder_needs_expunge = false; + Ok(()) } @@ -77,26 +78,7 @@ impl Imap { // deselect existing folder, if needed (it's also done implicitly by SELECT, however, without EXPUNGE then) let needs_expunge = { self.config.selected_folder_needs_expunge }; if needs_expunge { - if let Some(ref folder) = self.config.selected_folder { - info!(context, "Expunge messages in \"{}\".", folder); - - // A CLOSE-SELECT is considerably faster than an EXPUNGE-SELECT, see - // https://tools.ietf.org/html/rfc3501#section-6.4.2 - if let Some(ref mut session) = &mut self.session { - match session.close().await { - Ok(_) => { - info!(context, "close/expunge succeeded"); - } - Err(err) => { - self.trigger_reconnect(); - return Err(Error::CloseExpungeFailed(err)); - } - } - } else { - return Err(Error::NoSession); - } - } - self.config.selected_folder_needs_expunge = false; + self.close_folder(context).await?; } // select new folder diff --git a/src/job.rs b/src/job.rs index 208d42625..43b795c54 100644 --- a/src/job.rs +++ b/src/job.rs @@ -810,45 +810,6 @@ pub enum Connection<'a> { Smtp(&'a mut Smtp), } -async fn add_imap_deletion_jobs(context: &Context) -> sql::Result<()> { - if let Some(delete_server_after) = context.get_config_delete_server_after().await { - let threshold_timestamp = time() - delete_server_after; - - // Select all expired messages which don't have a - // corresponding message deletion job yet. - let msg_ids = context - .sql - .query_map( - "SELECT id FROM msgs \ - WHERE timestamp < ? \ - AND server_uid != 0 \ - AND NOT EXISTS (SELECT 1 FROM jobs WHERE foreign_id = msgs.id \ - AND action = ?)", - paramsv![threshold_timestamp, Action::DeleteMsgOnImap], - |row| row.get::<_, MsgId>(0), - |ids| { - ids.collect::, _>>() - .map_err(Into::into) - }, - ) - .await?; - - // Schedule IMAP deletion for expired messages. - for msg_id in msg_ids { - add( - context, - Action::DeleteMsgOnImap, - msg_id.to_u32() as i32, - Params::new(), - 0, - ) - .await; - } - } - - Ok(()) -} - async fn load_imap_deletion_msgid(context: &Context) -> sql::Result> { if let Some(delete_server_after) = context.get_config_delete_server_after().await { let threshold_timestamp = time() - delete_server_after; diff --git a/src/oauth2.rs b/src/oauth2.rs index 6bb657882..89354f651 100644 --- a/src/oauth2.rs +++ b/src/oauth2.rs @@ -75,8 +75,6 @@ pub async fn dc_get_oauth2_url( } } -// The following function may block due http-requests; -// must not be called from the main thread or by the ui! pub async fn dc_get_oauth2_access_token( context: &Context, addr: impl AsRef, @@ -84,9 +82,7 @@ pub async fn dc_get_oauth2_access_token( regenerate: bool, ) -> Option { if let Some(oauth2) = Oauth2::from_address(addr) { - // TODO: FIXME - // let lock = context.oauth2_critical.clone(); - // let _l = lock.lock().await; + let lock = context.oauth2_mutex.lock().await; // read generated token if !regenerate && !is_expired(context).await { @@ -243,6 +239,8 @@ pub async fn dc_get_oauth2_access_token( warn!(context, "Failed to find OAuth2 access token"); } + drop(lock); + response.access_token } else { warn!(context, "Internal OAuth2 error: 2"); From 84abe257f1c7c219f01bb60b9a67fb248ead74c0 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Tue, 19 May 2020 17:44:37 +0200 Subject: [PATCH 048/118] bring back intermediate job fetching --- src/scheduler.rs | 88 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 60 insertions(+), 28 deletions(-) diff --git a/src/scheduler.rs b/src/scheduler.rs index d51ad7aba..d0af4347f 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -65,44 +65,28 @@ async fn inbox_loop(ctx: Context, inbox_handlers: ImapConnectionHandlers) { return; } + // track number of continously executed jobs + let mut jobs_loaded = 0; loop { let probe_network = ctx.scheduler.read().await.get_probe_network(); match job::load_next(&ctx, Thread::Imap, probe_network) .timeout(Duration::from_millis(200)) .await { - Ok(Some(job)) => { + Ok(Some(job)) if jobs_loaded <= 20 => { + jobs_loaded += 1; job::perform_job(&ctx, job::Connection::Inbox(&mut connection), job).await; ctx.scheduler.write().await.set_probe_network(false); } + Ok(Some(job)) => { + // Let the fetch run, but return back to the job afterwards. + info!(ctx, "postponing imap-job {} to run fetch...", job); + jobs_loaded = 0; + fetch(&ctx, &mut connection).await; + } Ok(None) | Err(async_std::future::TimeoutError { .. }) => { - match get_watch_folder(&ctx, "configured_inbox_folder").await { - Some(watch_folder) => { - // fetch - connection - .fetch(&ctx, &watch_folder) - .await - .unwrap_or_else(|err| { - error!(ctx, "{}", err); - }); - - // idle - if connection.can_idle() { - connection - .idle(&ctx, Some(watch_folder)) - .await - .unwrap_or_else(|err| { - error!(ctx, "{}", err); - }); - } else { - connection.fake_idle(&ctx, Some(watch_folder)).await; - } - } - None => { - warn!(ctx, "Can not watch inbox folder, not set"); - connection.fake_idle(&ctx, None).await; - } - } + jobs_loaded = 0; + fetch_idle(&ctx, &mut connection).await; } } } @@ -113,6 +97,54 @@ async fn inbox_loop(ctx: Context, inbox_handlers: ImapConnectionHandlers) { shutdown_sender.send(()).await; } +async fn fetch(ctx: &Context, connection: &mut Imap) { + match get_watch_folder(&ctx, "configured_inbox_folder").await { + Some(watch_folder) => { + // fetch + connection + .fetch(&ctx, &watch_folder) + .await + .unwrap_or_else(|err| { + error!(ctx, "{}", err); + }); + } + None => { + warn!(ctx, "Can not fetch inbox folder, not set"); + connection.fake_idle(&ctx, None).await; + } + } +} + +async fn fetch_idle(ctx: &Context, connection: &mut Imap) { + match get_watch_folder(&ctx, "configured_inbox_folder").await { + Some(watch_folder) => { + // fetch + connection + .fetch(&ctx, &watch_folder) + .await + .unwrap_or_else(|err| { + error!(ctx, "{}", err); + }); + + // idle + if connection.can_idle() { + connection + .idle(&ctx, Some(watch_folder)) + .await + .unwrap_or_else(|err| { + error!(ctx, "{}", err); + }); + } else { + connection.fake_idle(&ctx, Some(watch_folder)).await; + } + } + None => { + warn!(ctx, "Can not watch inbox folder, not set"); + connection.fake_idle(&ctx, None).await; + } + } +} + async fn simple_imap_loop( ctx: Context, inbox_handlers: ImapConnectionHandlers, From 71f7a3e902c048569a7251e6348520c9741a18c2 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Tue, 19 May 2020 18:07:04 +0200 Subject: [PATCH 049/118] avoid clone --- src/location.rs | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/location.rs b/src/location.rs index 42a51583c..fee937438 100644 --- a/src/location.rs +++ b/src/location.rs @@ -524,8 +524,13 @@ pub async fn save( let mut newest_location_id = 0; for location in locations { - // TODO: can this clone be avoided? - let location = location.clone(); + let &Location { + timestamp, + latitude, + longitude, + accuracy, + .. + } = location; context .sql .with_conn(move |mut conn| { @@ -537,29 +542,29 @@ pub async fn save( VALUES (?,?,?,?,?,?,?);", )?; - let exists = stmt_test.exists(paramsv![location.timestamp, contact_id as i32])?; + let exists = stmt_test.exists(paramsv![timestamp, contact_id as i32])?; if independent || !exists { stmt_insert.execute(paramsv![ - location.timestamp, + timestamp, contact_id as i32, chat_id, - location.latitude, - location.longitude, - location.accuracy, + latitude, + longitude, + accuracy, independent, ])?; - if location.timestamp > newest_timestamp { + if timestamp > newest_timestamp { // okay to drop, as we use cached prepared statements drop(stmt_test); drop(stmt_insert); - newest_timestamp = location.timestamp; + newest_timestamp = timestamp; newest_location_id = crate::sql::get_rowid2( &mut conn, "locations", "timestamp", - location.timestamp, + timestamp, "from_id", contact_id as i32, )?; From 03fd311bfeeef9b381ed8e4f2391a1f641d073af Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 20 May 2020 13:04:44 +0200 Subject: [PATCH 050/118] log shutdown msg --- src/context.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/context.rs b/src/context.rs index edd436fa5..29c05d02b 100644 --- a/src/context.rs +++ b/src/context.rs @@ -151,6 +151,7 @@ impl Context { } pub async fn stop(&self) { + info!(self, "stopping context"); self.inner.stop().await; } From 3703a1c36c15d3b2c53002760c29bb5f8d3b2c53 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Wed, 20 May 2020 13:31:45 +0200 Subject: [PATCH 051/118] refinement startup --- python/src/deltachat/account.py | 20 ++++++++++++++++---- python/src/deltachat/iothreads.py | 4 ---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index 8c9643ffe..9cb620c15 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -17,7 +17,7 @@ from .message import Message, map_system_message from .contact import Contact from .tracker import ImexTracker from . import hookspec, iothreads -from .eventlogger import FFIEvent +from .eventlogger import FFIEvent, FFIEventLogger class MissingCredentials(ValueError): """ Account is missing `addr` and `mail_pw` config values. """ @@ -40,7 +40,10 @@ class Account(object): # initialize per-account plugin system self._pm = hookspec.PerAccount._make_plugin_manager() self._logging = logging + self.add_account_plugin(self) + if logging: + self.add_account_plugin(FFIEventLogger(self, logid=str(id(self))[:5])) self._dc_context = ffi.gc( lib.dc_context_new(ffi.NULL, as_dc_charpointer(os_name)), @@ -59,6 +62,7 @@ class Account(object): db_path = db_path.encode("utf8") if not lib.dc_open(self._dc_context, db_path, ffi.NULL): raise ValueError("Could not dc_open: {}".format(db_path)) + self._threads.start() self._configkeys = self.get_config("sys.config_keys").split() atexit.register(self.shutdown) hook.dc_account_init(account=self) @@ -461,7 +465,7 @@ class Account(object): If sending out was unsuccessful, a RuntimeError is raised. """ self.check_is_configured() - if not self._threads.is_started(): + if not self._threads.is_started() or not self.is_started(): raise RuntimeError("threads not running, can not send out") res = lib.dc_initiate_key_transfer(self._dc_context) if res == ffi.NULL: @@ -574,7 +578,9 @@ class Account(object): raise MissingCredentials("addr or mail_pwd not set in config") lib.dc_configure(self._dc_context) lib.dc_context_run(self._dc_context) - self._threads.start() + + def is_started(self): + return bool(lib.dc_is_running(self._dc_context)) def wait_shutdown(self): """ wait until shutdown of this account has completed. """ @@ -589,10 +595,14 @@ class Account(object): if self._threads.is_started(): self.stop_ongoing() + self.ac_log_line("stop threads") self._threads.stop(wait=False) - + + self.ac_log_line("context shutdown") lib.dc_context_shutdown(dc_context) + self.ac_log_line("dc_close") lib.dc_close(dc_context) + self.ac_log_line("wait threads for real") self._threads.stop(wait=wait) # to wait for threads self._dc_context = None atexit.unregister(self.shutdown) @@ -609,9 +619,11 @@ class Account(object): raise RuntimeError("can only call iter_events() from one thread") self._in_use_iter_events = True while lib.dc_is_open(self._dc_context): + self.ac_log_line("waiting for event") event = lib.dc_get_next_event(self._dc_context) if event == ffi.NULL: break + self.ac_log_line("got event {}".format(event)) evt = lib.dc_event_get_id(event) data1 = lib.dc_event_get_data1(event) diff --git a/python/src/deltachat/iothreads.py b/python/src/deltachat/iothreads.py index f4cc88faf..ce9fd8b16 100644 --- a/python/src/deltachat/iothreads.py +++ b/python/src/deltachat/iothreads.py @@ -37,10 +37,6 @@ class IOThreads: def stop(self, wait=False): self._thread_quitflag = True - # Workaround for a race condition. Make sure that thread is - # not in between checking for quitflag and entering idle. - time.sleep(0.5) - if wait: for name, thread in self._name2thread.items(): if thread != threading.currentThread(): From b91d7f314be7cf0cfa35e3792880e974794aca20 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Wed, 20 May 2020 14:26:18 +0200 Subject: [PATCH 052/118] progress --- python/src/deltachat/account.py | 19 ++++++++++++------ python/src/deltachat/iothreads.py | 10 +++++++++- python/tests/test_account.py | 15 ++++++++++++--- python/x.py | 32 +++++++++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 10 deletions(-) create mode 100644 python/x.py diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index 9cb620c15..44b9cac22 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -15,7 +15,7 @@ from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array, DCLot from .chat import Chat from .message import Message, map_system_message from .contact import Contact -from .tracker import ImexTracker +from .tracker import ImexTracker, ConfigureTracker from . import hookspec, iothreads from .eventlogger import FFIEvent, FFIEventLogger @@ -169,7 +169,7 @@ class Account(object): :returns: True if account is configured. """ - return bool(lib.dc_is_configured(self._dc_context)) + return True if lib.dc_is_configured(self._dc_context) else False def set_avatar(self, img_path): """Set self avatar. @@ -576,7 +576,9 @@ class Account(object): if not self.is_configured(): if not self.get_config("addr") or not self.get_config("mail_pw"): raise MissingCredentials("addr or mail_pwd not set in config") - lib.dc_configure(self._dc_context) + with self.temp_plugin(ConfigureTracker()) as config_tracker: + lib.dc_configure(self._dc_context) + config_tracker.wait_finish() lib.dc_context_run(self._dc_context) def is_started(self): @@ -586,6 +588,11 @@ class Account(object): """ wait until shutdown of this account has completed. """ self._shutdown_event.wait() + def stop_scheduler(self): + """ stop core scheduler if it is running. """ + self.ac_log_line("context_shutdown (stop core scheduler)") + lib.dc_context_shutdown(self._dc_context) + def shutdown(self, wait=True): """ shutdown account, stop threads and close and remove underlying dc_context.""" @@ -593,13 +600,13 @@ class Account(object): if dc_context is None: return + self.stop_ongoing() if self._threads.is_started(): - self.stop_ongoing() self.ac_log_line("stop threads") self._threads.stop(wait=False) - self.ac_log_line("context shutdown") - lib.dc_context_shutdown(dc_context) + self.stop_scheduler() + self.ac_log_line("dc_close") lib.dc_close(dc_context) self.ac_log_line("wait threads for real") diff --git a/python/src/deltachat/iothreads.py b/python/src/deltachat/iothreads.py index ce9fd8b16..62792b133 100644 --- a/python/src/deltachat/iothreads.py +++ b/python/src/deltachat/iothreads.py @@ -51,6 +51,14 @@ class IOThreads: except StopIteration: break self.account.ac_log_line("calling hook name={} kwargs={}".format(ev.name, ev.kwargs)) - ev.call_hook() + try: + ev.call_hook() + except Exception: + # don't bother logging this error + # because dc_close() was concurrently called + # and core API starts failing after that. + if not self._thread_quitflag: + raise + diff --git a/python/tests/test_account.py b/python/tests/test_account.py index 6034534af..3387163bb 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -473,8 +473,17 @@ class TestOfflineChat: num_contacts = len(chat.get_contacts()) assert num_contacts == 11 - # perform plugin hooks - ac1._handle_current_events() + # let's make sure the events perform plugin hooks + def wait_events(cond): + now = time.time() + while time.time() < now + 5: + if cond(): + break + time.sleep(0.1) + else: + pytest.fail("failed to get events") + + wait_events(lambda: len(in_list) == 10) assert len(in_list) == 10 chat_contacts = chat.get_contacts() @@ -493,7 +502,7 @@ class TestOfflineChat: chat.remove_contact(contacts[3]) assert len(chat.get_contacts()) == 9 - ac1._handle_current_events() + wait_events(lambda: len(in_list) == 2) assert len(in_list) == 2 assert in_list[0][0] == "removed" assert in_list[0][1] == chat diff --git a/python/x.py b/python/x.py new file mode 100644 index 000000000..6ad780bc3 --- /dev/null +++ b/python/x.py @@ -0,0 +1,32 @@ + +import deltachat +import os +import shutil +from deltachat.capi import lib + +try: + os.remove("/tmp/db") +except: + pass +try: + shutil.rmtree("/tmp/db-blobs") +except: + pass + + +acc = deltachat.Account("/tmp/db", logging=True) +acc.set_config("addr", "tmp.hjfcq@five.chat") +acc.set_config("mail_pw", "aihWNtLuRJgV") +acc.start() # lib.dc_configure + lib.dc_context_run +assert acc.is_configured() +acc.stop_scheduler() + +while 1: + print("starting scheduler") + acc.start() + print("stopping scheduler") + acc.stop_scheduler() + +contact = acc.create_contact("holger@deltachat.de") +chat = acc.create_chat_by_contact(contact) +chat.send_text("hello") From f67c86cb393fcac0cd7efdb299fa19d12cf5937f Mon Sep 17 00:00:00 2001 From: holger krekel Date: Wed, 20 May 2020 16:32:12 +0200 Subject: [PATCH 053/118] refactor callback thread handling --- python/src/deltachat/__init__.py | 4 +- python/src/deltachat/account.py | 107 +++----------------------- python/src/deltachat/eventlogger.py | 114 +++++++++++++++++++++++++--- python/src/deltachat/iothreads.py | 64 ---------------- python/tests/conftest.py | 1 + python/tests/test_account.py | 1 - python/tests/test_lowlevel.py | 53 +------------ 7 files changed, 123 insertions(+), 221 deletions(-) delete mode 100644 python/src/deltachat/iothreads.py diff --git a/python/src/deltachat/__init__.py b/python/src/deltachat/__init__.py index fbd20283d..1f88100f2 100644 --- a/python/src/deltachat/__init__.py +++ b/python/src/deltachat/__init__.py @@ -1,7 +1,7 @@ import sys -from . import capi, const, hookspec -from .capi import ffi +from . import capi, const, hookspec # noqa +from .capi import ffi # noqa from .account import Account # noqa from .message import Message # noqa from .contact import Contact # noqa diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index 44b9cac22..086955157 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -4,20 +4,19 @@ from __future__ import print_function import atexit from contextlib import contextmanager from email.utils import parseaddr -import queue from threading import Event import os from array import array -import deltachat from . import const from .capi import ffi, lib from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array, DCLot from .chat import Chat -from .message import Message, map_system_message +from .message import Message from .contact import Contact from .tracker import ImexTracker, ConfigureTracker -from . import hookspec, iothreads -from .eventlogger import FFIEvent, FFIEventLogger +from . import hookspec +from .eventlogger import FFIEventLogger, CallbackThread + class MissingCredentials(ValueError): """ Account is missing `addr` and `mail_pw` config values. """ @@ -52,8 +51,6 @@ class Account(object): hook = hookspec.Global._get_plugin_manager().hook - self._threads = iothreads.IOThreads(self) - self._in_use_iter_events = False self._shutdown_event = Event() # open database @@ -62,7 +59,7 @@ class Account(object): db_path = db_path.encode("utf8") if not lib.dc_open(self._dc_context, db_path, ffi.NULL): raise ValueError("Could not dc_open: {}".format(db_path)) - self._threads.start() + self._cb_thread = CallbackThread(self) self._configkeys = self.get_config("sys.config_keys").split() atexit.register(self.shutdown) hook.dc_account_init(account=self) @@ -465,8 +462,8 @@ class Account(object): If sending out was unsuccessful, a RuntimeError is raised. """ self.check_is_configured() - if not self._threads.is_started() or not self.is_started(): - raise RuntimeError("threads not running, can not send out") + if not self._cb_thread.is_alive() or not self.is_started(): + raise RuntimeError("IO not running, can not send out") res = lib.dc_initiate_key_transfer(self._dc_context) if res == ffi.NULL: raise RuntimeError("could not send out autocrypt setup message") @@ -591,6 +588,7 @@ class Account(object): def stop_scheduler(self): """ stop core scheduler if it is running. """ self.ac_log_line("context_shutdown (stop core scheduler)") + self.stop_ongoing() lib.dc_context_shutdown(self._dc_context) def shutdown(self, wait=True): @@ -600,90 +598,23 @@ class Account(object): if dc_context is None: return - self.stop_ongoing() - if self._threads.is_started(): + if self._cb_thread.is_alive(): self.ac_log_line("stop threads") - self._threads.stop(wait=False) + self._cb_thread.stop(wait=False) self.stop_scheduler() self.ac_log_line("dc_close") lib.dc_close(dc_context) self.ac_log_line("wait threads for real") - self._threads.stop(wait=wait) # to wait for threads + if wait: + self._cb_thread.stop(wait=wait) self._dc_context = None atexit.unregister(self.shutdown) self._shutdown_event.set() hook = hookspec.Global._get_plugin_manager().hook hook.dc_account_after_shutdown(account=self, dc_context=dc_context) - def iter_events(self, timeout=None): - """ yield hook events until shutdown. - - It is not allowed to call iter_events() from multiple threads. - """ - if self._in_use_iter_events: - raise RuntimeError("can only call iter_events() from one thread") - self._in_use_iter_events = True - while lib.dc_is_open(self._dc_context): - self.ac_log_line("waiting for event") - event = lib.dc_get_next_event(self._dc_context) - if event == ffi.NULL: - break - self.ac_log_line("got event {}".format(event)) - - evt = lib.dc_event_get_id(event) - data1 = lib.dc_event_get_data1(event) - data2 = lib.dc_event_get_data2(event) - # the following code relates to the deltachat/_build.py's helper - # function which provides us signature info of an event call - evt_name = deltachat.get_dc_event_name(evt) - event_sig_types = lib.dc_get_event_signature_types(evt) - if data1 and event_sig_types & 1: - data1 = ffi.string(ffi.gc(ffi.cast('char*', data1), lib.dc_str_unref)).decode("utf8") - if data2 and event_sig_types & 2: - data2 = ffi.string(ffi.gc(ffi.cast('char*', data2), lib.dc_str_unref)).decode("utf8") - try: - if isinstance(data2, bytes): - data2 = data2.decode("utf8") - except UnicodeDecodeError: - # XXX ignoring the decode error is not quite correct but for now - # i don't want to hunt down encoding problems in the c lib - pass - - lib.dc_event_unref(event) - ffi_event = FFIEvent(name=evt_name, data1=data1, data2=data2) - self._pm.hook.ac_process_ffi_event(account=self, ffi_event=ffi_event) - for name, kwargs in self._map_ffi_event(ffi_event): - yield HookEvent(self, name=name, kwargs=kwargs) - - def _map_ffi_event(self, ffi_event): - name = ffi_event.name - if name == "DC_EVENT_CONFIGURE_PROGRESS": - data1 = ffi_event.data1 - if data1 == 0 or data1 == 1000: - success = data1 == 1000 - yield "ac_configure_completed", dict(success=success) - elif name == "DC_EVENT_INCOMING_MSG": - msg = self.get_message_by_id(ffi_event.data2) - yield map_system_message(msg) or ("ac_incoming_message", dict(message=msg)) - elif name == "DC_EVENT_MSGS_CHANGED": - if ffi_event.data2 != 0: - msg = self.get_message_by_id(ffi_event.data2) - if msg.is_outgoing(): - res = map_system_message(msg) - if res and res[0].startswith("ac_member"): - yield res - yield "ac_outgoing_message", dict(message=msg) - elif msg.is_in_fresh(): - yield map_system_message(msg) or ("ac_incoming_message", dict(message=msg)) - elif name == "DC_EVENT_MSG_DELIVERED": - msg = self.get_message_by_id(ffi_event.data2) - yield "ac_message_delivered", dict(message=msg) - elif name == "DC_EVENT_CHAT_MODIFIED": - chat = self.get_chat_by_id(ffi_event.data1) - yield "ac_chat_modified", dict(chat=chat) - def _destroy_dc_context(dc_context, dc_context_unref=lib.dc_context_unref): # destructor for dc_context @@ -703,17 +634,3 @@ class ScannedQRCode: @property def contact_id(self): return self._dc_lot.id() - - -class HookEvent: - def __init__(self, account, name, kwargs): - assert hasattr(account._pm.hook, name), name - self.account = account - self.name = name - self.kwargs = kwargs - - def call_hook(self): - hook = getattr(self.account._pm.hook, self.name, None) - if hook is None: - raise ValueError("event_name {} unknown".format(self.name)) - return hook(**self.kwargs) diff --git a/python/src/deltachat/eventlogger.py b/python/src/deltachat/eventlogger.py index e225843e6..4a7f7e542 100644 --- a/python/src/deltachat/eventlogger.py +++ b/python/src/deltachat/eventlogger.py @@ -1,18 +1,13 @@ -import deltachat import threading import time import re from queue import Queue, Empty -from .hookspec import account_hookimpl, global_hookimpl - - -# @global_hookimpl -# def dc_account_init(account): - # account._threads.start() - -# @global_hookimpl -# def dc_account_after_shutdown(dc_context): +import deltachat +from .hookspec import account_hookimpl +from contextlib import contextmanager +from .capi import ffi, lib +from .message import map_system_message class FFIEvent: @@ -127,3 +122,102 @@ class FFIEventTracker: ev = self.get_matching("DC_EVENT_MSGS_CHANGED") if ev.data2 > 0: return self.account.get_message_by_id(ev.data2) + + +class CallbackThread(threading.Thread): + """ Callback Thread for an account. + + With each Account init this callback thread is started. + """ + def __init__(self, account): + self.account = account + self._dc_context = account._dc_context + self._thread_quitflag = False + super(CallbackThread, self).__init__(name="callback") + self.start() + + @contextmanager + def log_execution(self, message): + self.account.ac_log_line(message + " START") + yield + self.account.ac_log_line(message + " FINISHED") + + def stop(self, wait=False): + self._thread_quitflag = True + + if wait: + self.join() + + def run(self): + """ get and run events until shutdown. """ + with self.log_execution("CALLBACK THREAD START"): + self._inner_run() + + def _inner_run(self): + while lib.dc_is_open(self._dc_context) and not self._thread_quitflag: + self.account.ac_log_line("waiting for event") + event = lib.dc_get_next_event(self._dc_context) + if event == ffi.NULL: + break + self.account.ac_log_line("got event {}".format(event)) + + evt = lib.dc_event_get_id(event) + data1 = lib.dc_event_get_data1(event) + data2 = lib.dc_event_get_data2(event) + # the following code relates to the deltachat/_build.py's helper + # function which provides us signature info of an event call + evt_name = deltachat.get_dc_event_name(evt) + event_sig_types = lib.dc_get_event_signature_types(evt) + if data1 and event_sig_types & 1: + data1 = ffi.string(ffi.gc(ffi.cast('char*', data1), lib.dc_str_unref)).decode("utf8") + if data2 and event_sig_types & 2: + data2 = ffi.string(ffi.gc(ffi.cast('char*', data2), lib.dc_str_unref)).decode("utf8") + try: + if isinstance(data2, bytes): + data2 = data2.decode("utf8") + except UnicodeDecodeError: + # XXX ignoring the decode error is not quite correct but for now + # i don't want to hunt down encoding problems in the c lib + pass + + lib.dc_event_unref(event) + ffi_event = FFIEvent(name=evt_name, data1=data1, data2=data2) + self.account._pm.hook.ac_process_ffi_event(account=self, ffi_event=ffi_event) + for name, kwargs in self._map_ffi_event(ffi_event): + self.account.ac_log_line("calling hook name={} kwargs={}".format(name, kwargs)) + hook = getattr(self.account._pm.hook, name) + try: + hook(**kwargs) + except Exception: + # don't bother logging this error + # if dc_close() was concurrently called + # (note: core API starts failing after that) + if not self._thread_quitflag: + raise + + def _map_ffi_event(self, ffi_event): + name = ffi_event.name + if name == "DC_EVENT_CONFIGURE_PROGRESS": + data1 = ffi_event.data1 + if data1 == 0 or data1 == 1000: + success = data1 == 1000 + yield "ac_configure_completed", dict(success=success) + elif name == "DC_EVENT_INCOMING_MSG": + msg = self.get_message_by_id(ffi_event.data2) + yield map_system_message(msg) or ("ac_incoming_message", dict(message=msg)) + elif name == "DC_EVENT_MSGS_CHANGED": + if ffi_event.data2 != 0: + msg = self.account.get_message_by_id(ffi_event.data2) + if msg.is_outgoing(): + res = map_system_message(msg) + if res and res[0].startswith("ac_member"): + yield res + yield "ac_outgoing_message", dict(message=msg) + elif msg.is_in_fresh(): + yield map_system_message(msg) or ("ac_incoming_message", dict(message=msg)) + elif name == "DC_EVENT_MSG_DELIVERED": + msg = self.account.get_message_by_id(ffi_event.data2) + yield "ac_message_delivered", dict(message=msg) + elif name == "DC_EVENT_CHAT_MODIFIED": + chat = self.account.get_chat_by_id(ffi_event.data1) + yield "ac_chat_modified", dict(chat=chat) diff --git a/python/src/deltachat/iothreads.py b/python/src/deltachat/iothreads.py deleted file mode 100644 index 62792b133..000000000 --- a/python/src/deltachat/iothreads.py +++ /dev/null @@ -1,64 +0,0 @@ - -import threading -import time - -from contextlib import contextmanager - -from .capi import ffi, lib -import deltachat -from .eventlogger import FFIEvent - -class IOThreads: - def __init__(self, account): - self.account = account - self._dc_context = account._dc_context - self._thread_quitflag = False - self._name2thread = {} - - def is_started(self): - return len(self._name2thread) > 0 - - def start(self): - assert not self.is_started() - - self._start_one_thread("cb", self.cb_thread_run) - - def _start_one_thread(self, name, func): - self._name2thread[name] = t = threading.Thread(target=func, name=name) - t.setDaemon(1) - t.start() - - @contextmanager - def log_execution(self, message): - self.account.ac_log_line(message + " START") - yield - self.account.ac_log_line(message + " FINISHED") - - def stop(self, wait=False): - self._thread_quitflag = True - - if wait: - for name, thread in self._name2thread.items(): - if thread != threading.currentThread(): - thread.join() - - def cb_thread_run(self): - with self.log_execution("CALLBACK THREAD START"): - it = self.account.iter_events() - while not self._thread_quitflag: - try: - ev = next(it) - except StopIteration: - break - self.account.ac_log_line("calling hook name={} kwargs={}".format(ev.name, ev.kwargs)) - try: - ev.call_hook() - except Exception: - # don't bother logging this error - # because dc_close() was concurrently called - # and core API starts failing after that. - if not self._thread_quitflag: - raise - - - diff --git a/python/tests/conftest.py b/python/tests/conftest.py index 109085739..6cc65dc14 100644 --- a/python/tests/conftest.py +++ b/python/tests/conftest.py @@ -1,5 +1,6 @@ from __future__ import print_function + def wait_configuration_progress(account, min_target, max_target=1001, check_error=True): min_target = min(min_target, max_target) while 1: diff --git a/python/tests/test_account.py b/python/tests/test_account.py index 3387163bb..9855085d2 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -1686,4 +1686,3 @@ class TestOnlineConfigureFails: ev = ac1._evtracker.get_matching("DC_EVENT_ERROR_NETWORK") assert "could not connect" in ev.data2.lower() wait_configuration_progress(ac1, 0, 0) - diff --git a/python/tests/test_lowlevel.py b/python/tests/test_lowlevel.py index 9161f4dc1..247f265f9 100644 --- a/python/tests/test_lowlevel.py +++ b/python/tests/test_lowlevel.py @@ -1,7 +1,6 @@ from __future__ import print_function -import threading -import time +from queue import Queue from deltachat import capi, cutil, const from deltachat import register_global_plugin from deltachat.hookspec import global_hookimpl @@ -10,34 +9,6 @@ from deltachat.capi import lib # from deltachat.account import EventLogger -class EventThread(threading.Thread): - def __init__(self, dc_context): - self.dc_context = dc_context - super(EventThread, self).__init__() - self.setDaemon(1) - self._running = True - - def run(self): - lib.dc_context_run(self.dc_context) - while self._running: - if lib.dc_has_next_event(self.dc_context): - event = lib.dc_get_next_event(self.dc_context) - if event != ffi.NULL: - py_dc_callback( - self._dc_context, - lib.dc_event_get_id(event), - lib.dc_event_get_data1(event), - lib.dc_event_get_data2(event) - ) - lib.dc_event_unref(event) - else: - time.sleep(0.05) - - def stop(self): - lib.dc_context_shutdown(self.dc_context) - self._running = False - - def test_empty_context(): ctx = capi.lib.dc_context_new(capi.ffi.NULL, capi.ffi.NULL) capi.lib.dc_close(ctx) @@ -47,33 +18,17 @@ def test_dc_close_events(tmpdir, acfactory): ac1 = acfactory.get_unconfigured_account() # register after_shutdown function - shutdowns = [] + shutdowns = Queue() class ShutdownPlugin: @global_hookimpl def dc_account_after_shutdown(self, account): assert account._dc_context is None - shutdowns.append(account) + shutdowns.put(account) register_global_plugin(ShutdownPlugin()) assert hasattr(ac1, "_dc_context") ac1.shutdown() - assert shutdowns == [ac1] - - def find(info_string): - evlog = ac1._evtracker - while 1: - ev = evlog.get_matching("DC_EVENT_INFO", check_error=False) - data2 = ev.data2 - if info_string in data2: - return - else: - print("skipping event", ev) - - find("disconnecting inbox-thread") - find("disconnecting sentbox-thread") - find("disconnecting mvbox-thread") - find("disconnecting SMTP") - find("Database closed") + shutdowns.get(timeout=2) def test_wrong_db(tmpdir): From 4855584de9f42055554acc6c877640a85f5bc584 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Wed, 20 May 2020 16:37:27 +0200 Subject: [PATCH 054/118] rename eventlogger to events --- python/src/deltachat/__init__.py | 6 +++--- python/src/deltachat/account.py | 2 +- python/src/deltachat/{eventlogger.py => events.py} | 0 python/src/deltachat/testplugin.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) rename python/src/deltachat/{eventlogger.py => events.py} (100%) diff --git a/python/src/deltachat/__init__.py b/python/src/deltachat/__init__.py index 1f88100f2..db637d017 100644 --- a/python/src/deltachat/__init__.py +++ b/python/src/deltachat/__init__.py @@ -7,7 +7,7 @@ from .message import Message # noqa from .contact import Contact # noqa from .chat import Chat # noqa from .hookspec import account_hookimpl, global_hookimpl # noqa -from . import eventlogger +from . import events from pkg_resources import get_distribution, DistributionNotFound try: @@ -39,7 +39,7 @@ def unregister_global_plugin(plugin): gm.unregister(plugin) -register_global_plugin(eventlogger) +register_global_plugin(events) def run_cmdline(argv=None, account_plugins=None): @@ -60,7 +60,7 @@ def run_cmdline(argv=None, account_plugins=None): ac = Account(args.db) if args.show_ffi: - log = eventlogger.FFIEventLogger(ac, "bot") + log = events.FFIEventLogger(ac, "bot") ac.add_account_plugin(log) if not ac.is_configured(): diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index 086955157..049fe61aa 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -15,7 +15,7 @@ from .message import Message from .contact import Contact from .tracker import ImexTracker, ConfigureTracker from . import hookspec -from .eventlogger import FFIEventLogger, CallbackThread +from .events import FFIEventLogger, CallbackThread class MissingCredentials(ValueError): diff --git a/python/src/deltachat/eventlogger.py b/python/src/deltachat/events.py similarity index 100% rename from python/src/deltachat/eventlogger.py rename to python/src/deltachat/events.py diff --git a/python/src/deltachat/testplugin.py b/python/src/deltachat/testplugin.py index 7208e8280..d38788361 100644 --- a/python/src/deltachat/testplugin.py +++ b/python/src/deltachat/testplugin.py @@ -15,7 +15,7 @@ import requests from . import Account, const from .tracker import ConfigureTracker from .capi import lib -from .eventlogger import FFIEventLogger, FFIEventTracker +from .events import FFIEventLogger, FFIEventTracker from _pytest.monkeypatch import MonkeyPatch from _pytest._code import Source From 3c7b3faa7faeb03c0407881e4062328acbfe52ee Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 20 May 2020 16:42:18 +0200 Subject: [PATCH 055/118] unify deps and more strict start and stop --- Cargo.lock | 2601 +++++++++++++++---------------- Cargo.toml | 12 +- deltachat-ffi/Cargo.toml | 3 +- deltachat-ffi/src/lib.rs | 1 - python/src/deltachat/account.py | 3 + src/context.rs | 24 +- src/scheduler.rs | 127 +- 7 files changed, 1389 insertions(+), 1382 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 839f7be4e..5415be215 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,846 +4,878 @@ name = "addr2line" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a49806b9dadc843c61e7c97e72490ad7f7220ae249012fbda9ad0609457c0543" dependencies = [ - "gimli 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gimli", ] [[package]] name = "adler32" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" [[package]] name = "aes" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54eb1d8fe354e5fc611daf4f2ea97dd45a765f4f1e4512306ec183ae2e8f20c9" dependencies = [ - "aes-soft 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "aesni 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "aes-soft", + "aesni", + "block-cipher-trait", ] [[package]] name = "aes-soft" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfd7e7ae3f9a1fb5c03b389fc6bb9a51400d0c13053f0dca698c832bfd893a0d" dependencies = [ - "block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "block-cipher-trait", + "byteorder", + "opaque-debug", ] [[package]] name = "aesni" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f70a6b5f971e473091ab7cfb5ffac6cde81666c4556751d8d5620ead8abf100" dependencies = [ - "block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "block-cipher-trait", + "opaque-debug", ] [[package]] name = "aho-corasick" version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" dependencies = [ - "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr", ] [[package]] name = "ansi_term" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" dependencies = [ - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8", ] [[package]] name = "ansi_term" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" dependencies = [ - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8", ] [[package]] name = "anyhow" version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f" [[package]] name = "arrayref" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" [[package]] name = "arrayvec" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" dependencies = [ - "nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "nodrop", ] [[package]] name = "arrayvec" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" [[package]] name = "ascii_utils" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a" [[package]] name = "async-attributes" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd3d156917d94862e779f356c5acae312b08fd3121e792c857d7928c8088423" dependencies = [ - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.22 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.6", + "syn 1.0.22", ] [[package]] name = "async-imap" version = "0.2.0" -source = "git+https://github.com/async-email/async-imap?branch=feat/send#f55b9af2ca0ff99acbfc5ca30b24748501b5ef61" +source = "git+https://github.com/async-email/async-imap#5e338db3b664ebcfb437dddda035fc18a78cc3ea" dependencies = [ - "async-native-tls 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "byte-pool 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "imap-proto 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "nom 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rental 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", - "stop-token 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "thiserror 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", + "async-native-tls", + "async-std", + "base64 0.11.0", + "byte-pool", + "chrono", + "futures", + "imap-proto", + "lazy_static", + "log", + "nom 5.1.1", + "pin-utils", + "rental", + "stop-token", + "thiserror", ] [[package]] name = "async-native-tls" version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" +source = "git+https://github.com/async-email/async-native-tls#bda408c86e84e4fa9382f50820dadcae74924320" dependencies = [ - "async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", - "thiserror 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", - "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util", + "native-tls", + "thiserror", + "url", ] [[package]] name = "async-smtp" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" +source = "git+https://github.com/async-email/async-smtp#b2ffb61eb2ec4202c67013bfb3c7e22727c15228" dependencies = [ - "async-native-tls 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "async-trait 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)", - "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "bufstream 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "fast_chemail 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)", - "hostname 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "nom 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project 0.4.17 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)", - "thiserror 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", + "async-native-tls", + "async-std", + "async-trait", + "base64 0.11.0", + "bufstream", + "fast_chemail", + "hostname", + "log", + "nom 5.1.1", + "pin-project", + "pin-utils", + "serde", + "serde_derive", + "serde_json", + "thiserror", ] [[package]] name = "async-std" -version = "1.5.0" +version = "1.6.0-beta.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3bca717ee7c0abd18e11ba8bc12170e7eee097f96597a021129686bfcfd4978" dependencies = [ - "async-task 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "broadcaster 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-timer 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "kv-log-macro 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.22 (registry+https://github.com/rust-lang/crates.io-index)", - "mio-uds 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)", - "once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project-lite 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "async-std" -version = "1.6.0-beta.1" -source = "git+https://github.com/async-rs/async-std#9e6a76af04a08d3e3d13f4366c5d254dc2c22b94" -dependencies = [ - "async-attributes 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "async-task 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-timer 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "kv-log-macro 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)", - "once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project-lite 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "smol 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-futures 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "async-task" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "async-attributes", + "async-task", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-timer", + "kv-log-macro", + "log", + "memchr", + "num_cpus", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "smol", + "wasm-bindgen-futures", ] [[package]] name = "async-task" version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17772156ef2829aadc587461c7753af20b7e8db1529bc66855add962a3b35d3" [[package]] name = "async-trait" version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c4f3195085c36ea8d24d32b2f828d23296a9370a28aa39d111f6f16bef9f3b" dependencies = [ - "proc-macro2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.22 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote 1.0.6", + "syn 1.0.22", ] [[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "hermit-abi", + "libc", + "winapi 0.3.8", ] [[package]] name = "autocfg" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" [[package]] name = "autocfg" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" [[package]] name = "backtrace" version = "0.3.48" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0df2f85c8a2abbe3b7d7e748052fdd9b76a0458fdeb16ad4223f5eca78c7c130" dependencies = [ - "addr2line 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "object 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "addr2line", + "cfg-if", + "libc", + "object", + "rustc-demangle", ] [[package]] name = "base64" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" dependencies = [ - "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder", ] [[package]] name = "base64" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" [[package]] name = "bit-set" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" dependencies = [ - "bit-vec 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bit-vec", ] [[package]] name = "bit-vec" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0dc55f2d8a1a85650ac47858bb001b4c0dd73d79e3c455a842925e68d29cd3" [[package]] name = "bitfield" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" [[package]] name = "bitflags" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "blake2b_simd" version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a" dependencies = [ - "arrayref 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "arrayref", + "arrayvec 0.5.1", + "constant_time_eq", ] [[package]] name = "block-buffer" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" dependencies = [ - "block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", + "block-padding", + "byte-tools", + "byteorder", + "generic-array", ] [[package]] name = "block-cipher-trait" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c924d49bd09e7c06003acda26cd9742e796e34282ec6c1189404dee0c1f4774" dependencies = [ - "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", + "generic-array", ] [[package]] name = "block-modes" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31aa8410095e39fdb732909fb5730a48d5bd7c2e3cd76bd1b07b3dbea130c529" dependencies = [ - "block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "block-cipher-trait", + "block-padding", ] [[package]] name = "block-padding" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" dependencies = [ - "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "byte-tools", ] [[package]] name = "blowfish" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aeb80d00f2688459b8542068abd974cfb101e7a82182414a99b5026c0d85cc3" dependencies = [ - "block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "broadcaster" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "block-cipher-trait", + "byteorder", + "opaque-debug", ] [[package]] name = "buf_redux" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" dependencies = [ - "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "safemem 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr", + "safemem", ] [[package]] name = "bufstream" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8" [[package]] name = "bumpalo" version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5356f1d23ee24a1f785a56d1d1a5f0fd5b0f6a0c0fb2412ce11da71649ab78f6" [[package]] name = "byte-pool" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9342e102eac8b1879fbedf9a7e0572c40b0cc5805b663c4d4ca791cae0bae221" dependencies = [ - "crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-queue", + "stable_deref_trait", ] [[package]] name = "byte-tools" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" [[package]] name = "bytecount" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b92204551573580e078dc80017f36a213eb77a0450e4ddd8cfa0f3f2d1f0178f" [[package]] name = "byteorder" version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" [[package]] name = "bytes" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" [[package]] name = "cargo_metadata" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d1b4d380e1bab994591a24c2bdd1b054f64b60bef483a8c598c7c345bc3bbe" dependencies = [ - "error-chain 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)", - "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)", + "error-chain", + "semver", + "serde", + "serde_derive", + "serde_json", ] [[package]] name = "cast5" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ce5759b4c52ca74f9a98421817c882f1fd9b0071ae41cd61ab9f9d059c04fd6" dependencies = [ - "block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "block-cipher-trait", + "byteorder", + "opaque-debug", ] [[package]] name = "cc" -version = "1.0.53" +version = "1.0.54" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311" [[package]] name = "cfb-mode" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190e7b55d3a27cf8879becf61035a141cbc783f3258a41d16d1706719f991345" dependencies = [ - "block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "stream-cipher 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "block-cipher-trait", + "stream-cipher", ] [[package]] name = "cfg-if" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "charset" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f426e64df1c3de26cbf44593c6ffff5dbfd43bbf9de0d075058558126b3fc73" dependencies = [ - "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", - "encoding_rs 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)", + "base64 0.10.1", + "encoding_rs", ] [[package]] name = "chrono" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" dependencies = [ - "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer", + "num-traits", + "time", ] [[package]] name = "circular" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fc239e0f6cb375d2402d48afb92f76f5404fd1df208a41930ec81eda078bea" [[package]] name = "clear_on_drop" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97276801e127ffb46b66ce23f35cc96bd454fa311294bced4bbace7baa8b1d17" dependencies = [ - "cc 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)", + "cc", ] [[package]] name = "cloudabi" version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags", ] [[package]] name = "color_quant" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dbbb57365263e881e805dc77d94697c9118fd94d8da011240555aa7b23445bd" [[package]] name = "constant_time_eq" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "core-foundation" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" dependencies = [ - "core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation-sys", + "libc", ] [[package]] name = "core-foundation-sys" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" [[package]] name = "crc24" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd121741cf3eb82c08dd3023eb55bf2665e5f60ec20f89760cf836ae4562e6a0" [[package]] name = "crc32fast" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if", ] [[package]] name = "crossbeam" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69323bff1fb41c635347b8ead484a5ca6c3f11914d784170b158d8449ab07f8e" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if", + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", ] [[package]] name = "crossbeam-channel" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061" dependencies = [ - "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils", + "maybe-uninit", ] [[package]] name = "crossbeam-deque" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" dependencies = [ - "crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-epoch", + "crossbeam-utils", + "maybe-uninit", ] [[package]] name = "crossbeam-epoch" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" dependencies = [ - "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "memoffset 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", - "scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 1.0.0", + "cfg-if", + "crossbeam-utils", + "lazy_static", + "maybe-uninit", + "memoffset", + "scopeguard", ] [[package]] name = "crossbeam-queue" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if", + "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" dependencies = [ - "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 1.0.0", + "cfg-if", + "lazy_static", ] [[package]] name = "ctor" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf6b25ee9ac1995c54d7adb2eff8cfffb7260bc774fb63c601ec65467f43cd9d" dependencies = [ - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.22 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.6", + "syn 1.0.22", ] [[package]] name = "curve25519-dalek" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26778518a7f6cffa1d25a44b602b62b979bd88adb9e99ffec546998cf3404839" dependencies = [ - "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "subtle 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder", + "digest", + "rand_core 0.5.1", + "subtle", + "zeroize", ] [[package]] name = "darling" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" dependencies = [ - "darling_core 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", - "darling_macro 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", + "darling_core", + "darling_macro", ] [[package]] name = "darling_core" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" dependencies = [ - "fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "ident_case 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "strsim 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.22 (registry+https://github.com/rust-lang/crates.io-index)", + "fnv", + "ident_case", + "proc-macro2", + "quote 1.0.6", + "strsim", + "syn 1.0.22", ] [[package]] name = "darling_macro" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" dependencies = [ - "darling_core 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.22 (registry+https://github.com/rust-lang/crates.io-index)", + "darling_core", + "quote 1.0.6", + "syn 1.0.22", ] [[package]] name = "debug_stub_derive" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "496b7f8a2f853313c3ca370641d7ff3e42c32974fdccda8f0684599ed0a3ff6b" dependencies = [ - "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.3.15", + "syn 0.11.11", ] [[package]] name = "deflate" version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707b6a7b384888a70c8d2e8650b3e60170dfc6a67bb4aa67b6dfca57af4bedb4" dependencies = [ - "adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "adler32", + "byteorder", ] [[package]] name = "deltachat" version = "1.33.0" dependencies = [ - "ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", - "anyhow 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)", - "async-imap 0.2.0 (git+https://github.com/async-email/async-imap?branch=feat/send)", - "async-native-tls 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "async-smtp 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "async-std 1.6.0-beta.1 (git+https://github.com/async-rs/async-std)", - "async-trait 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)", - "backtrace 0.3.48 (registry+https://github.com/rust-lang/crates.io-index)", - "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "charset 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "debug_stub_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "deltachat_derive 2.0.0", - "email 0.0.21 (git+https://github.com/deltachat/rust-email)", - "encoded-words 0.1.0 (git+https://github.com/async-email/encoded-words)", - "escaper 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "hex 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "image 0.22.5 (registry+https://github.com/rust-lang/crates.io-index)", - "image-meta 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lettre_email 0.9.2 (git+https://github.com/deltachat/lettre)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "mailparse 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", - "native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", - "num-derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pgp 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", - "pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", - "pretty_env_logger 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "proptest 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)", - "quick-xml 0.17.2 (registry+https://github.com/rust-lang/crates.io-index)", - "r2d2 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)", - "r2d2_sqlite 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)", - "reqwest 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)", - "rusqlite 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rustyline 4.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "sanitize-filename 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)", - "sha2 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "stop-token 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "strum 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", - "strum_macros 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "thiserror 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", - "thread-local-object 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ansi_term 0.12.1", + "anyhow", + "async-imap", + "async-native-tls", + "async-smtp", + "async-std", + "async-trait", + "backtrace", + "base64 0.11.0", + "bitflags", + "byteorder", + "charset", + "chrono", + "crossbeam-channel", + "debug_stub_derive", + "deltachat_derive", + "email", + "encoded-words", + "escaper", + "futures", + "hex", + "image", + "image-meta", + "indexmap", + "itertools", + "lazy_static", + "lettre_email", + "libc", + "log", + "mailparse", + "native-tls", + "num-derive", + "num-traits", + "percent-encoding", + "pgp", + "pretty_assertions", + "pretty_env_logger", + "proptest", + "quick-xml", + "r2d2", + "r2d2_sqlite", + "rand 0.7.3", + "regex", + "reqwest", + "rusqlite", + "rustyline", + "sanitize-filename", + "serde", + "serde_json", + "sha2", + "smallvec", + "stop-token", + "strum", + "strum_macros", + "tempfile", + "thiserror", + "thread-local-object", ] [[package]] name = "deltachat_derive" version = "2.0.0" dependencies = [ - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.22 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.6", + "syn 1.0.22", ] [[package]] name = "deltachat_ffi" version = "1.33.0" dependencies = [ - "anyhow 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)", - "async-std 1.6.0-beta.1 (git+https://github.com/async-rs/async-std)", - "deltachat 1.33.0", - "human-panic 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)", - "thiserror 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", + "anyhow", + "async-std", + "deltachat", + "human-panic", + "libc", + "num-traits", + "serde_json", + "thiserror", ] [[package]] name = "derive_builder" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2658621297f2cf68762a6f7dc0bb7e1ff2cfd6583daef8ee0fed6f7ec468ec0" dependencies = [ - "darling 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", - "derive_builder_core 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.22 (registry+https://github.com/rust-lang/crates.io-index)", + "darling", + "derive_builder_core", + "proc-macro2", + "quote 1.0.6", + "syn 1.0.22", ] [[package]] name = "derive_builder_core" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef" dependencies = [ - "darling 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.22 (registry+https://github.com/rust-lang/crates.io-index)", + "darling", + "proc-macro2", + "quote 1.0.6", + "syn 1.0.22", ] [[package]] name = "des" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74ba5f1b5aee9772379c2670ba81306e65a93c0ee3caade7a1d22b188d88a3af" dependencies = [ - "block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "block-cipher-trait", + "byteorder", + "opaque-debug", ] [[package]] name = "difference" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" [[package]] name = "digest" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" dependencies = [ - "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", + "generic-array", ] [[package]] name = "dirs" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" dependencies = [ - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_users 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "redox_users", + "winapi 0.3.8", ] [[package]] name = "dtoa" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3" [[package]] name = "ed25519-dalek" version = "1.0.0-pre.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978710b352437433c97b2bff193f2fb1dfd58a093f863dd95e225a19baa599a2" dependencies = [ - "clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "curve25519-dalek 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "sha2 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "clear_on_drop", + "curve25519-dalek", + "rand 0.7.3", + "sha2", ] [[package]] name = "either" version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" [[package]] name = "email" version = "0.0.21" source = "git+https://github.com/deltachat/rust-email#ace12ee6f8e054dd890589f588d0311604fc25f0" dependencies = [ - "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", - "encoded-words 0.1.0 (git+https://github.com/async-email/encoded-words)", - "encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", - "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", + "base64 0.11.0", + "chrono", + "encoded-words", + "encoding", + "lazy_static", + "rand 0.7.3", + "time", + "version_check 0.9.1", ] [[package]] @@ -851,611 +883,675 @@ name = "encoded-words" version = "0.1.0" source = "git+https://github.com/async-email/encoded-words#2631c258183620f6d976abffabbfc2dcc697d793" dependencies = [ - "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "charset 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "encoding_rs 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)", - "hex 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)", - "thiserror 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", + "base64 0.11.0", + "charset", + "encoding_rs", + "hex", + "lazy_static", + "regex", + "thiserror", ] [[package]] name = "encoding" version = "0.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec" dependencies = [ - "encoding-index-japanese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)", - "encoding-index-korean 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)", - "encoding-index-simpchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)", - "encoding-index-singlebyte 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)", - "encoding-index-tradchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)", + "encoding-index-japanese", + "encoding-index-korean", + "encoding-index-simpchinese", + "encoding-index-singlebyte", + "encoding-index-tradchinese", ] [[package]] name = "encoding-index-japanese" version = "1.20141219.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91" dependencies = [ - "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "encoding_index_tests", ] [[package]] name = "encoding-index-korean" version = "1.20141219.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81" dependencies = [ - "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "encoding_index_tests", ] [[package]] name = "encoding-index-simpchinese" version = "1.20141219.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7" dependencies = [ - "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "encoding_index_tests", ] [[package]] name = "encoding-index-singlebyte" version = "1.20141219.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a" dependencies = [ - "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "encoding_index_tests", ] [[package]] name = "encoding-index-tradchinese" version = "1.20141219.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18" dependencies = [ - "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "encoding_index_tests", ] [[package]] name = "encoding_index_tests" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" [[package]] name = "encoding_rs" version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8ac63f94732332f44fe654443c46f6375d1939684c17b0afb6cb56b0456e171" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if", ] [[package]] name = "entities" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca" [[package]] name = "env_logger" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3" dependencies = [ - "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", - "humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)", - "termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "atty", + "humantime", + "log", + "regex", + "termcolor", ] [[package]] name = "error-chain" version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d371106cc88ffdfb1eabd7111e432da544f16f3e2d7bf1dfe8bf575f1df045cd" dependencies = [ - "backtrace 0.3.48 (registry+https://github.com/rust-lang/crates.io-index)", - "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace", + "version_check 0.9.1", ] [[package]] name = "escaper" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39da344028c2227132b2dfa7c186e2104ecc153467583d00ed9c398f9ff693b0" dependencies = [ - "entities 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "entities", ] [[package]] name = "failure" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" dependencies = [ - "backtrace 0.3.48 (registry+https://github.com/rust-lang/crates.io-index)", - "failure_derive 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace", + "failure_derive", ] [[package]] name = "failure_derive" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" dependencies = [ - "proc-macro2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.22 (registry+https://github.com/rust-lang/crates.io-index)", - "synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote 1.0.6", + "syn 1.0.22", + "synstructure", ] [[package]] name = "fake-simd" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" [[package]] name = "fallible-iterator" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fallible-streaming-iterator" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] name = "fast_chemail" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "495a39d30d624c2caabe6312bfead73e7717692b44e0b32df168c275a2e8e9e4" dependencies = [ - "ascii_utils 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", + "ascii_utils", ] [[package]] name = "flate2" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cfff41391129e0a856d6d822600b8d71179d46879e310417eb9c762eb178b42" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "miniz_oxide 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if", + "crc32fast", + "libc", + "miniz_oxide", ] [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foreign-types" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ - "foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "foreign-types-shared", ] [[package]] name = "foreign-types-shared" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "fuchsia-cprng" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" [[package]] name = "fuchsia-zircon" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags", + "fuchsia-zircon-sys", ] [[package]] name = "fuchsia-zircon-sys" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" [[package]] name = "futures" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e05b85ec287aac0dc34db7d4a569323df697f9c55b99b15d6b4ef8cde49f613" dependencies = [ - "futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-executor 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", ] [[package]] name = "futures-channel" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5" dependencies = [ - "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core", + "futures-sink", ] [[package]] name = "futures-core" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399" [[package]] name = "futures-executor" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d6bb888be1153d3abeb9006b11b02cf5e9b209fda28693c31ae1e4e012e314" dependencies = [ - "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core", + "futures-task", + "futures-util", ] [[package]] name = "futures-io" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789" [[package]] name = "futures-macro" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39" dependencies = [ - "proc-macro-hack 0.5.15 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.22 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-hack", + "proc-macro2", + "quote 1.0.6", + "syn 1.0.22", ] [[package]] name = "futures-sink" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc" [[package]] name = "futures-task" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626" dependencies = [ - "once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "once_cell", ] -[[package]] -name = "futures-timer" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "futures-timer" version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" dependencies = [ - "gloo-timers 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "send_wrapper 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gloo-timers", + "send_wrapper", ] [[package]] name = "futures-util" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6" dependencies = [ - "futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-macro 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project 0.4.17 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-hack 0.5.15 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-nested 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project", + "pin-utils", + "proc-macro-hack", + "proc-macro-nested", + "slab", ] [[package]] name = "generic-array" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" dependencies = [ - "typenum 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "typenum", ] [[package]] name = "getrandom" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if", + "libc", + "wasi", ] [[package]] name = "gif" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "471d90201b3b223f3451cd4ad53e34295f16a1df17b1edf3736d47761c3981af" dependencies = [ - "color_quant 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "lzw 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "color_quant", + "lzw", ] [[package]] name = "gimli" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc8e0c9bce37868955864dbecd2b1ab2bdf967e6f28066d65aaac620444b65c" [[package]] name = "glob" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" [[package]] name = "gloo-timers" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f" dependencies = [ - "futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "js-sys 0.3.39 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "web-sys 0.3.39 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", + "web-sys", ] [[package]] name = "h2" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79b7246d7e4b979c03fa093da39cfb3617a96bbeee6310af63991668d7e843ff" dependencies = [ - "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", - "fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "log", + "slab", + "tokio", + "tokio-util", ] [[package]] name = "heck" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" dependencies = [ - "unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-segmentation", ] [[package]] name = "hermit-abi" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71" dependencies = [ - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", ] [[package]] name = "hex" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" [[package]] name = "hostname" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21ceb46a83a85e824ef93669c8b390009623863b5c195d1ba747292c0c72f94e" dependencies = [ - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "winutil 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "winutil", ] [[package]] name = "http" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9" dependencies = [ - "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", - "fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes", + "fnv", + "itoa", ] [[package]] name = "http-body" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" dependencies = [ - "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", - "http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes", + "http", ] [[package]] name = "httparse" version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" [[package]] name = "human-panic" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39f357a500abcbd7c5f967c1d45c8838585b36743823b9d43488f24850534e36" dependencies = [ - "backtrace 0.3.48 (registry+https://github.com/rust-lang/crates.io-index)", - "os_type 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", - "termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", - "uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace", + "os_type", + "serde", + "serde_derive", + "termcolor", + "toml", + "uuid", ] [[package]] name = "humantime" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" dependencies = [ - "quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "quick-error", ] [[package]] name = "hyper" version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96816e1d921eca64d208a85aab4f7798455a8e34229ee5a88c935bdee1b78b14" dependencies = [ - "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "h2 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", - "http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "http-body 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project 0.4.17 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", - "tower-service 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "want 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "log", + "net2", + "pin-project", + "time", + "tokio", + "tower-service", + "want", ] [[package]] name = "hyper-tls" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3adcd308402b9553630734e9c36b77a7e48b3821251ca2493e8cd596763aafaa" dependencies = [ - "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper 0.13.5 (registry+https://github.com/rust-lang/crates.io-index)", - "native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-tls 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-tls", ] [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" dependencies = [ - "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-normalization 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "matches", + "unicode-bidi", + "unicode-normalization", ] [[package]] name = "image" version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08ed2ada878397b045454ac7cfb011d73132c59f31a955d230bd1f1c2e68eb4a" dependencies = [ - "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "gif 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)", - "jpeg-decoder 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)", - "num-iter 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", - "num-rational 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "png 0.15.3 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder", + "gif", + "jpeg-decoder", + "num-iter", + "num-rational", + "num-traits", + "png", ] [[package]] name = "image-meta" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a2da7b4225d0954c9b8ba1a0dcec85be29f496cba4d85f9390426f810e3ab0d" dependencies = [ - "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "skeptic 0.13.4 (registry+https://github.com/rust-lang/crates.io-index)", - "thiserror 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder", + "skeptic", + "thiserror", ] [[package]] name = "imap-proto" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16a6def1d5ac8975d70b3fd101d57953fe3278ef2ee5d7816cba54b1d1dfc22f" dependencies = [ - "nom 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "nom 5.1.1", ] [[package]] name = "indexmap" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292" dependencies = [ - "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 1.0.0", ] [[package]] name = "inflate" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cdb29978cc5797bd8dcc8e5bf7de604891df2a8dc576973d71a281e916db2ff" dependencies = [ - "adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "adler32", ] [[package]] name = "iovec" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" dependencies = [ - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", ] [[package]] name = "itertools" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" dependencies = [ - "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "either", ] [[package]] name = "itoa" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" [[package]] name = "jpeg-decoder" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b47b4c4e017b01abdc5bcc126d2d1002e5a75bbe3ce73f9f4f311a916363704" dependencies = [ - "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder", ] [[package]] name = "js-sys" version = "0.3.39" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa5a448de267e7358beaf4a5d849518fe9a0c13fce7afd44b06e68550e5562a7" dependencies = [ - "wasm-bindgen 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen", ] [[package]] name = "keccak" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" [[package]] name = "kernel32-sys" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8", + "winapi-build", ] [[package]] name = "kv-log-macro" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2d3beed37e5483887d81eb39de6de03a8346531410e1306ca48a9a89bd3a51" dependencies = [ - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "log", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" dependencies = [ - "spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "spin", ] [[package]] @@ -1463,8 +1559,8 @@ name = "lettre" version = "0.9.2" source = "git+https://github.com/deltachat/lettre#12e4f22d8d691fb5d1606e4c9034f5d4d9ba635b" dependencies = [ - "fast_chemail 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "fast_chemail", + "log", ] [[package]] @@ -1472,2138 +1568,1965 @@ name = "lettre_email" version = "0.9.2" source = "git+https://github.com/deltachat/lettre#12e4f22d8d691fb5d1606e4c9034f5d4d9ba635b" dependencies = [ - "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "email 0.0.21 (git+https://github.com/deltachat/rust-email)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lettre 0.9.2 (git+https://github.com/deltachat/lettre)", - "mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", - "uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "base64 0.11.0", + "email", + "lazy_static", + "lettre", + "mime", + "regex", + "time", + "uuid", ] [[package]] name = "lexical-core" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7043aa5c05dd34fb73b47acb8c3708eac428de4545ea3682ed2f11293ebd890" dependencies = [ - "arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ryu 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "arrayvec 0.4.12", + "cfg-if", + "rustc_version", + "ryu", + "static_assertions", ] [[package]] name = "libc" version = "0.2.70" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3baa92041a6fec78c687fa0cc2b3fae8884f743d672cf551bed1d6dac6988d0f" [[package]] name = "libm" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" [[package]] name = "libsqlite3-sys" version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56d90181c2904c287e5390186be820e5ef311a3c62edebb7d6ca3d6a48ce041d" dependencies = [ - "cc 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", - "vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "cc", + "pkg-config", + "vcpkg", ] [[package]] name = "linked-hash-map" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" [[package]] name = "lock_api" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" dependencies = [ - "scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "scopeguard", ] [[package]] name = "log" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if", ] [[package]] name = "lru-cache" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" dependencies = [ - "linked-hash-map 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "linked-hash-map", ] [[package]] name = "lzw" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084" [[package]] name = "mailparse" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6435afa64615b3c3efa09cb4066f42dcd236902f30ab48acd36ea59821bb4e0" dependencies = [ - "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", - "charset 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "quoted_printable 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "base64 0.10.1", + "charset", + "quoted_printable", ] [[package]] name = "matches" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" [[package]] name = "maybe-uninit" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" [[package]] name = "md-5" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18af3dcaf2b0219366cdb4e2af65a6101457b415c3d1a5c71dd9c2b7c77b9c8" dependencies = [ - "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "block-buffer", + "digest", + "opaque-debug", ] [[package]] name = "memchr" version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" [[package]] name = "memoffset" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4fc2c02a7e374099d4ee95a193111f72d2110197fe200272371758f6c3643d8" dependencies = [ - "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 1.0.0", ] [[package]] name = "mime" version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" [[package]] name = "mime_guess" version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" dependencies = [ - "mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", - "unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "mime", + "unicase", ] [[package]] name = "miniz_oxide" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa679ff6578b1cddee93d7e82e263b94a575e0bfced07284eb0c037c1d2416a5" dependencies = [ - "adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "adler32", ] [[package]] name = "mio" version = "0.6.22" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "mio-uds" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.22 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log", + "miow", + "net2", + "slab", + "winapi 0.2.8", ] [[package]] name = "miow" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" dependencies = [ - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", ] [[package]] name = "native-tls" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b0d88c06fe90d5ee94048ba40409ef1d9315d86f6f38c2efdaad4fb50c58b2d" dependencies = [ - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl 0.10.29 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.9.56 (registry+https://github.com/rust-lang/crates.io-index)", - "schannel 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework-sys 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", - "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", ] [[package]] name = "net2" version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if", + "libc", + "winapi 0.3.8", ] [[package]] name = "nix" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dbdc256eaac2e3bd236d93ad999d3479ef775c863dbda3068c4006a92eec51b" dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "cc 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags", + "cc", + "cfg-if", + "libc", + "void", ] [[package]] name = "nix" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "cc 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags", + "cc", + "cfg-if", + "libc", + "void", ] [[package]] name = "nodrop" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" [[package]] name = "nom" version = "4.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" dependencies = [ - "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr", + "version_check 0.1.5", ] [[package]] name = "nom" version = "5.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b471253da97532da4b61552249c521e01e736071f71c1a4f7ebbfbf0a06aad6" dependencies = [ - "lexical-core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lexical-core", + "memchr", + "version_check 0.9.1", ] [[package]] name = "num-bigint-dig" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d03c330f9f7a2c19e3c0b42698e48141d0809c78cd9b6219f85bd7d7e892aa" dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libm 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", - "num-iter 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 0.1.7", + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.7.3", + "serde", + "smallvec", + "zeroize", ] [[package]] name = "num-derive" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8b15b261814f992e33760b1fca9fe8b693d8a65299f20c9901688636cfb746" dependencies = [ - "proc-macro2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.22 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote 1.0.6", + "syn 1.0.22", ] [[package]] name = "num-integer" version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" dependencies = [ - "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 1.0.0", + "num-traits", ] [[package]] name = "num-iter" version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfb0800a0291891dd9f4fe7bd9c19384f98f7fbe0cd0f39a2c6b88b9868bbc00" dependencies = [ - "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 1.0.0", + "num-integer", + "num-traits", ] [[package]] name = "num-rational" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" dependencies = [ - "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 1.0.0", + "num-integer", + "num-traits", ] [[package]] name = "num-traits" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" dependencies = [ - "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 1.0.0", ] [[package]] name = "num_cpus" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" dependencies = [ - "hermit-abi 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", + "hermit-abi", + "libc", ] [[package]] name = "object" version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cbca9424c482ee628fa549d9c812e2cd22f1180b9222c9200fdfa6eb31aecb2" [[package]] name = "once_cell" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" [[package]] name = "opaque-debug" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" [[package]] name = "openssl" version = "0.10.29" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cee6d85f4cb4c4f59a6a85d5b68a233d280c82e29e822913b9c8b129fbf20bdd" dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.9.56 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags", + "cfg-if", + "foreign-types", + "lazy_static", + "libc", + "openssl-sys", ] [[package]] name = "openssl-probe" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" [[package]] name = "openssl-src" version = "111.9.0+1.1.1g" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2dbe10ddd1eb335aba3780eb2eaa13e1b7b441d2562fd962398740927f39ec4" dependencies = [ - "cc 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)", + "cc", ] [[package]] name = "openssl-sys" version = "0.9.56" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02309a7f127000ed50594f0b50ecc69e7c654e16d41b4e8156d1b3df8e0b52e" dependencies = [ - "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cc 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-src 111.9.0+1.1.1g (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", - "vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 1.0.0", + "cc", + "libc", + "openssl-src", + "pkg-config", + "vcpkg", ] [[package]] name = "os_type" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edc011af0ae98b7f88cf7e4a83b70a54a75d2b8cb013d6efd02e5956207e9eb" dependencies = [ - "regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "regex", ] [[package]] name = "output_vt100" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" dependencies = [ - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8", ] [[package]] name = "packed_simd" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a85ea9fc0d4ac0deb6fe7911d38786b32fc11119afd9e9d38b84ff691ce64220" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if", ] [[package]] name = "parking_lot" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" dependencies = [ - "lock_api 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot_core 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lock_api", + "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if", + "cloudabi", + "libc", + "redox_syscall", + "smallvec", + "winapi 0.3.8", ] [[package]] name = "percent-encoding" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pgp" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8172973101790c866e66966002bf1028d0df27bf6b3b29be86a6fd440d8a4285" dependencies = [ - "aes 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "bitfield 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)", - "block-modes 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "blowfish 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "buf_redux 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "cast5 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cfb-mode 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", - "circular 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "crc24 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "derive_builder 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "des 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "ed25519-dalek 1.0.0-pre.3 (registry+https://github.com/rust-lang/crates.io-index)", - "flate2 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", - "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", - "hex 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "md-5 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "num-bigint-dig 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "num-derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ripemd160 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rsa 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "sha-1 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", - "sha2 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "sha3 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "thiserror 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", - "try_from 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "twofish 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "x25519-dalek 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "aes", + "base64 0.11.0", + "bitfield", + "block-modes", + "block-padding", + "blowfish", + "buf_redux", + "byteorder", + "cast5", + "cfb-mode", + "chrono", + "circular", + "clear_on_drop", + "crc24", + "derive_builder", + "des", + "digest", + "ed25519-dalek", + "flate2", + "generic-array", + "hex", + "lazy_static", + "log", + "md-5", + "nom 4.2.3", + "num-bigint-dig", + "num-derive", + "num-traits", + "rand 0.7.3", + "ripemd160", + "rsa", + "sha-1", + "sha2", + "sha3", + "smallvec", + "thiserror", + "try_from", + "twofish", + "x25519-dalek", + "zeroize", ] [[package]] name = "pin-project" version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edc93aeee735e60ecb40cf740eb319ff23eab1c5748abfdb5c180e4ce49f7791" dependencies = [ - "pin-project-internal 0.4.17 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-project-internal", ] [[package]] name = "pin-project-internal" version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e58db2081ba5b4c93bd6be09c40fd36cb9193a8336c384f3b40012e531aa7e40" dependencies = [ - "proc-macro2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.22 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote 1.0.6", + "syn 1.0.22", ] [[package]] name = "pin-project-lite" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7505eeebd78492e0f6108f7171c4948dbb120ee8119d9d77d0afa5469bef67f" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "piper" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6d62a6ea407d82215154475927b288219b79c8670e3371166210328e758ebaa" dependencies = [ - "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils", + "futures", ] [[package]] name = "pkg-config" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" [[package]] name = "png" version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef859a23054bbfee7811284275ae522f0434a3c8e7f4b74bd4a35ae7e1c4a283" dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "deflate 0.7.20 (registry+https://github.com/rust-lang/crates.io-index)", - "inflate 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags", + "crc32fast", + "deflate", + "inflate", ] [[package]] name = "ppv-lite86" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" [[package]] name = "pretty_assertions" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427" dependencies = [ - "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "ctor 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", - "difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "output_vt100 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "ansi_term 0.11.0", + "ctor", + "difference", + "output_vt100", ] [[package]] name = "pretty_env_logger" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "717ee476b1690853d222af4634056d830b5197ffd747726a9a1eee6da9f49074" dependencies = [ - "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", - "env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono", + "env_logger", + "log", ] [[package]] name = "proc-macro-hack" version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63" [[package]] name = "proc-macro-nested" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694" [[package]] name = "proc-macro2" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53f5ffe53a6b28e37c9c1ce74893477864d64f74778a93a4beb43c8fa167f639" dependencies = [ - "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.2.0", ] [[package]] name = "proptest" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c477819b845fe023d33583ebf10c9f62518c8d79a0960ba5c36d6ac8a55a5b" dependencies = [ - "bit-set 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)", - "rusty-fork 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bit-set", + "bitflags", + "byteorder", + "lazy_static", + "num-traits", + "quick-error", + "rand 0.6.5", + "rand_chacha 0.1.1", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", ] [[package]] name = "pulldown-cmark" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eef52fac62d0ea7b9b4dc7da092aa64ea7ec3d90af6679422d3d7e0e14b6ee15" dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags", ] [[package]] name = "quick-error" version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quick-xml" version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe1e430bdcf30c9fdc25053b9c459bb1a4672af4617b6c783d7d91dc17c6bbb0" dependencies = [ - "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr", ] [[package]] name = "quote" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" [[package]] name = "quote" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea" dependencies = [ - "proc-macro2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", ] [[package]] name = "quoted_printable" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b080c5db639b292ac79cbd34be0cfc5d36694768d8341109634d90b86930e2" [[package]] name = "r2d2" version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1497e40855348e4a8a40767d8e55174bce1e445a3ac9254ad44ad468ee0485af" dependencies = [ - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", - "scheduled-thread-pool 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "log", + "parking_lot", + "scheduled-thread-pool", ] [[package]] name = "r2d2_sqlite" version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e15ff794e7c8bb8ae20ccac5bac6a93a4a3af708dd801d4094f80da41196f33" dependencies = [ - "r2d2 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)", - "rusqlite 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", + "r2d2", + "rusqlite", ] [[package]] name = "rand" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" dependencies = [ - "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi 0.3.8", ] [[package]] name = "rand" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 0.1.7", + "libc", + "rand_chacha 0.1.1", + "rand_core 0.4.2", + "rand_hc 0.1.0", + "rand_isaac", + "rand_jitter", + "rand_os", + "rand_pcg", + "rand_xorshift", + "winapi 0.3.8", ] [[package]] name = "rand" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ - "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "packed_simd 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom", + "libc", + "packed_simd", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc 0.2.0", ] [[package]] name = "rand_chacha" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 0.1.7", + "rand_core 0.3.1", ] [[package]] name = "rand_chacha" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" dependencies = [ - "ppv-lite86 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ppv-lite86", + "rand_core 0.5.1", ] [[package]] name = "rand_core" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" dependencies = [ - "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.2", ] [[package]] name = "rand_core" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" [[package]] name = "rand_core" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" dependencies = [ - "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom", ] [[package]] name = "rand_hc" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" dependencies = [ - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1", ] [[package]] name = "rand_hc" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" dependencies = [ - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1", ] [[package]] name = "rand_isaac" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" dependencies = [ - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1", ] [[package]] name = "rand_jitter" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" dependencies = [ - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "rand_core 0.4.2", + "winapi 0.3.8", ] [[package]] name = "rand_os" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" dependencies = [ - "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "cloudabi", + "fuchsia-cprng", + "libc", + "rand_core 0.4.2", + "rdrand", + "winapi 0.3.8", ] [[package]] name = "rand_pcg" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 0.1.7", + "rand_core 0.4.2", ] [[package]] name = "rand_xorshift" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" dependencies = [ - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1", ] [[package]] name = "rdrand" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" dependencies = [ - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1", ] [[package]] name = "redox_syscall" version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" [[package]] name = "redox_users" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431" dependencies = [ - "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", - "rust-argon2 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom", + "redox_syscall", + "rust-argon2", ] [[package]] name = "regex" version = "1.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692" dependencies = [ - "aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)", - "thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", ] [[package]] name = "regex-syntax" version = "0.6.17" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" [[package]] name = "remove_dir_all" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" dependencies = [ - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8", ] [[package]] name = "rental" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8545debe98b2b139fb04cad8618b530e9b07c152d99a5de83c860b877d67847f" dependencies = [ - "rental-impl 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", - "stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rental-impl", + "stable_deref_trait", ] [[package]] name = "rental-impl" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "475e68978dc5b743f2f40d8e0a8fdc83f1c5e78cbf4b8fa5e74e73beebc340de" dependencies = [ - "proc-macro2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.22 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote 1.0.6", + "syn 1.0.22", ] [[package]] name = "reqwest" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b81e49ddec5109a9dcfc5f2a317ff53377c915e9ae9d4f2fb50914b85614e2" dependencies = [ - "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", - "encoding_rs 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "http-body 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper 0.13.5 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper-tls 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "js-sys 0.3.39 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", - "mime_guess 2.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", - "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project-lite 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-tls 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-futures 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "web-sys 0.3.39 (registry+https://github.com/rust-lang/crates.io-index)", - "winreg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "base64 0.11.0", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "hyper-tls", + "js-sys", + "lazy_static", + "log", + "mime", + "mime_guess", + "native-tls", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "time", + "tokio", + "tokio-tls", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", ] [[package]] name = "ripemd160" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad5112e0dbbb87577bfbc56c42450235e3012ce336e29c5befd7807bd626da4a" dependencies = [ - "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "block-buffer", + "digest", + "opaque-debug", ] [[package]] name = "rsa" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed8692d8e0ea3baae03f0f32ecfc13a6c6f1f85fcd6d9fdefcdf364e70f4df9" dependencies = [ - "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "num-bigint-dig 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", - "num-iter 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "subtle 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder", + "failure", + "lazy_static", + "num-bigint-dig", + "num-integer", + "num-iter", + "num-traits", + "rand 0.7.3", + "subtle", + "zeroize", ] [[package]] name = "rusqlite" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57edf4c4cea4d7e0fab069acb5da9e8e8e5403c78abc81b1f37d83af02148ea5" dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "fallible-iterator 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "fallible-streaming-iterator 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "libsqlite3-sys 0.17.3 (registry+https://github.com/rust-lang/crates.io-index)", - "lru-cache 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "libsqlite3-sys", + "lru-cache", + "memchr", + "time", ] [[package]] name = "rust-argon2" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017" dependencies = [ - "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "blake2b_simd 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)", - "constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "base64 0.11.0", + "blake2b_simd", + "constant_time_eq", + "crossbeam-utils", ] [[package]] name = "rustc-demangle" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" [[package]] name = "rustc_version" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" dependencies = [ - "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "semver", ] [[package]] name = "rusty-fork" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dd93264e10c577503e926bd1430193eeb5d21b059148910082245309b424fae" dependencies = [ - "fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "wait-timeout 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "fnv", + "quick-error", + "tempfile", + "wait-timeout", ] [[package]] name = "rustyline" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f47ea1ceb347d2deae482d655dc8eef4bd82363d3329baffa3818bd76fea48b" dependencies = [ - "dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "nix 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "utf8parse 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "dirs", + "libc", + "log", + "memchr", + "nix 0.13.1", + "unicode-segmentation", + "unicode-width", + "utf8parse", + "winapi 0.3.8", ] [[package]] name = "ryu" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1" [[package]] name = "safemem" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ - "winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-util", ] [[package]] name = "sanitize-filename" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23fd0fec94ec480abfd86bb8f4f6c57e0efb36dac5c852add176ea7b04c74801" dependencies = [ - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static", + "regex", ] [[package]] name = "schannel" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" dependencies = [ - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static", + "winapi 0.3.8", ] [[package]] name = "scheduled-thread-pool" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0988d7fdf88d5e5fcf5923a0f1e8ab345f3e98ab4bc6bc45a2d5ff7f7458fbf6" dependencies = [ - "parking_lot 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot", ] [[package]] name = "scoped-tls-hkt" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e9d7eaddb227e8fbaaa71136ae0e1e913ca159b86c7da82f3e8f0044ad3a63" [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "security-framework" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64808902d7d99f78eaddd2b4e2509713babc3dc3c85ad6f4c447680f3c01e535" dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework-sys 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", ] [[package]] name = "security-framework-sys" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17bf11d99252f512695eb468de5516e5cf75455521e69dfe343f3b74e4748405" dependencies = [ - "core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation-sys", + "libc", ] [[package]] name = "semver" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" dependencies = [ - "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", + "semver-parser", + "serde", ] [[package]] name = "semver-parser" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "send_wrapper" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" [[package]] name = "serde" version = "1.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99e7b308464d16b56eba9964e4972a3eee817760ab60d88c3f86e1fecb08204c" dependencies = [ - "serde_derive 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984" dependencies = [ - "proc-macro2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.22 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote 1.0.6", + "syn 1.0.22", ] [[package]] name = "serde_json" version = "1.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993948e75b189211a9b31a7528f950c6adc21f9720b6438ff80a7fa2f864cea2" dependencies = [ - "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "ryu 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa", + "ryu", + "serde", ] [[package]] name = "serde_urlencoded" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97" dependencies = [ - "dtoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", - "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "dtoa", + "itoa", + "serde", + "url", ] [[package]] name = "sha-1" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" dependencies = [ - "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "block-buffer", + "digest", + "fake-simd", + "opaque-debug", ] [[package]] name = "sha2" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27044adfd2e1f077f649f59deb9490d3941d674002f7d062870a60ebe9bd47a0" dependencies = [ - "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "block-buffer", + "digest", + "fake-simd", + "opaque-debug", ] [[package]] name = "sha3" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd26bc0e7a2e3a7c959bc494caf58b72ee0c71d67704e9520f736ca7e4853ecf" dependencies = [ - "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "keccak 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "block-buffer", + "byte-tools", + "digest", + "keccak", + "opaque-debug", ] [[package]] name = "skeptic" version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6fb8ed853fdc19ce09752d63f3a2e5b5158aeb261520cd75eb618bd60305165" dependencies = [ - "bytecount 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cargo_metadata 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", - "error-chain 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)", - "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "pulldown-cmark 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)", - "tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", - "walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bytecount", + "cargo_metadata", + "error-chain", + "glob", + "pulldown-cmark", + "serde_json", + "tempdir", + "walkdir", ] [[package]] name = "slab" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" [[package]] name = "smallvec" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" [[package]] name = "smol" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1014c048f35c553bfd0155079a62ab9a0e220f9e39f49244c1d58f825ab84a" dependencies = [ - "async-task 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "nix 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)", - "once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "piper 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "scoped-tls-hkt 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "socket2 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)", - "wepoll-binding 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "async-task", + "crossbeam", + "futures-io", + "futures-util", + "nix 0.17.0", + "once_cell", + "piper", + "scoped-tls-hkt", + "slab", + "socket2", + "wepoll-binding", ] [[package]] name = "socket2" version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if", + "libc", + "redox_syscall", + "winapi 0.3.8", ] [[package]] name = "spin" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "stable_deref_trait" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" [[package]] name = "static_assertions" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3" [[package]] name = "stop-token" version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" +source = "git+https://github.com/dignifiedquire/stop-token#05f56fa8e968fc79230a9a00c49d027b9ecee176" dependencies = [ - "async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project-lite 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "async-std", + "pin-project-lite", ] [[package]] name = "stream-cipher" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8131256a5896cabcf5eb04f4d6dacbe1aefda854b0d9896e09cb58829ec5638c" dependencies = [ - "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", + "generic-array", ] [[package]] name = "strsim" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" [[package]] name = "strum" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6138f8f88a16d90134763314e3fc76fa3ed6a7db4725d6acf9a3ef95a3188d22" [[package]] name = "strum_macros" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0054a7df764039a6cd8592b9de84be4bec368ff081d203a7d5371cbfa8e65c81" dependencies = [ - "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.22 (registry+https://github.com/rust-lang/crates.io-index)", + "heck", + "proc-macro2", + "quote 1.0.6", + "syn 1.0.22", ] [[package]] name = "subtle" version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c65d530b10ccaeac294f349038a597e435b18fb456aadd0840a623f83b9e941" [[package]] name = "syn" version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" dependencies = [ - "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", - "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.3.15", + "synom", + "unicode-xid 0.0.4", ] [[package]] name = "syn" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1425de3c33b0941002740a420b1a906a350b88d08b82b2c8a01035a3f9447bac" dependencies = [ - "proc-macro2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote 1.0.6", + "unicode-xid 0.2.0", ] [[package]] name = "synom" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" dependencies = [ - "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.0.4", ] [[package]] name = "synstructure" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" dependencies = [ - "proc-macro2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.22 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote 1.0.6", + "syn 1.0.22", + "unicode-xid 0.2.0", ] [[package]] name = "tempdir" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" dependencies = [ - "rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.4.6", + "remove_dir_all", ] [[package]] name = "tempfile" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", - "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if", + "libc", + "rand 0.7.3", + "redox_syscall", + "remove_dir_all", + "winapi 0.3.8", ] [[package]] name = "termcolor" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" dependencies = [ - "winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-util", ] [[package]] name = "thiserror" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5976891d6950b4f68477850b5b9e5aa64d955961466f9e174363f573e54e8ca7" dependencies = [ - "thiserror-impl 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab81dbd1cd69cd2ce22ecfbdd3bdb73334ba25350649408cc6c085f46d89573d" dependencies = [ - "proc-macro2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.22 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote 1.0.6", + "syn 1.0.22", ] [[package]] name = "thread-local-object" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da3caa820d0308c84c8654f6cafd81cc3195d45433311cbe22fcf44fc8be071" dependencies = [ - "unsafe-any 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "unsafe-any", ] [[package]] name = "thread_local" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" dependencies = [ - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static", ] [[package]] name = "time" version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" dependencies = [ - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "winapi 0.3.8", ] [[package]] name = "tokio" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d099fa27b9702bed751524694adbe393e18b36b204da91eb1cbbbbb4a5ee2d58" dependencies = [ - "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", - "fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.22 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project-lite 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes", + "fnv", + "futures-core", + "iovec", + "lazy_static", + "memchr", + "mio", + "num_cpus", + "pin-project-lite", + "slab", ] [[package]] name = "tokio-tls" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a70f4fcd7b3b24fb194f837560168208f669ca8cb70d0c4b862944452396343" dependencies = [ - "native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls", + "tokio", ] [[package]] name = "tokio-util" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" dependencies = [ - "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project-lite 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", ] [[package]] name = "toml" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" dependencies = [ - "serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", + "serde", ] [[package]] name = "tower-service" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860" [[package]] name = "traitobject" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" [[package]] name = "try-lock" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" [[package]] name = "try_from" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "283d3b89e1368717881a9d51dad843cc435380d8109c9e47d38780a324698d8b" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if", ] [[package]] name = "twofish" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712d261e83e727c8e2dbb75dacac67c36e35db36a958ee504f2164fc052434e1" dependencies = [ - "block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "block-cipher-trait", + "byteorder", + "opaque-debug", ] [[package]] name = "typenum" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" [[package]] name = "unicase" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" dependencies = [ - "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", + "version_check 0.9.1", ] [[package]] name = "unicode-bidi" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" dependencies = [ - "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "matches", ] [[package]] name = "unicode-normalization" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4" dependencies = [ - "smallvec 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec", ] [[package]] name = "unicode-segmentation" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" [[package]] name = "unicode-width" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" [[package]] name = "unicode-xid" version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" [[package]] name = "unicode-xid" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" [[package]] name = "unsafe-any" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30360d7979f5e9c6e6cea48af192ea8fab4afb3cf72597154b8f08935bc9c7f" dependencies = [ - "traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "traitobject", ] [[package]] name = "url" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" dependencies = [ - "idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "idna", + "matches", + "percent-encoding", ] [[package]] name = "utf8parse" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8772a4ccbb4e89959023bc5b7cb8623a795caa7092d99f3aa9501b9484d4557d" [[package]] name = "uuid" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" dependencies = [ - "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.3", ] [[package]] name = "vcpkg" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" [[package]] name = "version_check" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" [[package]] name = "version_check" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" [[package]] name = "void" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" [[package]] name = "wait-timeout" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" dependencies = [ - "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", ] [[package]] name = "walkdir" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" dependencies = [ - "same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "same-file", + "winapi 0.3.8", + "winapi-util", ] [[package]] name = "want" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" dependencies = [ - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "log", + "try-lock", ] [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasm-bindgen" version = "0.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c7d40d09cdbf0f4895ae58cf57d92e1e57a9dd8ed2e8390514b54a47cc5551" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-macro 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if", + "serde", + "serde_json", + "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3972e137ebf830900db522d6c8fd74d1900dcfc733462e9a12e942b00b4ac94" dependencies = [ - "bumpalo 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.22 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-shared 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote 1.0.6", + "syn 1.0.22", + "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a369c5e1dfb7569e14d62af4da642a3cbc2f9a3652fe586e26ac22222aa4b04" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "js-sys 0.3.39 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "web-sys 0.3.39 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cd85aa2c579e8892442954685f0d801f9129de24fa2136b2c6a539c76b65776" dependencies = [ - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-macro-support 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.6", + "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eb197bd3a47553334907ffd2f16507b4f4f01bbec3ac921a7719e0decdfe72a" dependencies = [ - "proc-macro2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.22 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-backend 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-shared 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote 1.0.6", + "syn 1.0.22", + "wasm-bindgen-backend", + "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a91c2916119c17a8e316507afaaa2dd94b47646048014bbdf6bef098c1bb58ad" [[package]] name = "web-sys" version = "0.3.39" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bc359e5dd3b46cb9687a051d50a2fdd228e4ba7cf6fcf861a5365c3d671a642" dependencies = [ - "js-sys 0.3.39 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "js-sys", + "wasm-bindgen", ] [[package]] name = "wepoll-binding" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7511014d92edaa2cba607fd6dc62974915bebc3e16f91f911a4bb240b4a3a68c" dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "wepoll-sys 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags", + "wepoll-sys", ] [[package]] name = "wepoll-sys" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9082a777aed991f6769e2b654aa0cb29f1c3d615daf009829b07b66c7aff6a24" dependencies = [ - "cc 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)", + "cc", ] [[package]] name = "winapi" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" [[package]] name = "winapi" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" dependencies = [ - "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-build" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "winreg" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" dependencies = [ - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8", ] [[package]] name = "winutil" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7daf138b6b14196e3830a588acf1e86966c694d3e8fb026fb105b8b5dca07e6e" dependencies = [ - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8", ] [[package]] name = "ws2_32-sys" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8", + "winapi-build", ] [[package]] name = "x25519-dalek" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "637ff90c9540fa3073bb577e65033069e4bae7c79d49d74aa3ffdf5342a53217" dependencies = [ - "curve25519-dalek 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "curve25519-dalek", + "rand_core 0.5.1", + "zeroize", ] [[package]] name = "zeroize" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cbac2ed2ba24cc90f5e06485ac8c7c1e5449fe8911aef4d8877218af021a5b8" dependencies = [ - "zeroize_derive 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "zeroize_derive", ] [[package]] name = "zeroize_derive" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de251eec69fc7c1bc3923403d18ececb929380e016afe103da75f396704f8ca2" dependencies = [ - "proc-macro2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.22 (registry+https://github.com/rust-lang/crates.io-index)", - "synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote 1.0.6", + "syn 1.0.22", + "synstructure", ] - -[metadata] -"checksum addr2line 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a49806b9dadc843c61e7c97e72490ad7f7220ae249012fbda9ad0609457c0543" -"checksum adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" -"checksum aes 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "54eb1d8fe354e5fc611daf4f2ea97dd45a765f4f1e4512306ec183ae2e8f20c9" -"checksum aes-soft 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cfd7e7ae3f9a1fb5c03b389fc6bb9a51400d0c13053f0dca698c832bfd893a0d" -"checksum aesni 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2f70a6b5f971e473091ab7cfb5ffac6cde81666c4556751d8d5620ead8abf100" -"checksum aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)" = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" -"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -"checksum ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -"checksum anyhow 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)" = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f" -"checksum arrayref 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" -"checksum arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" -"checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" -"checksum ascii_utils 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a" -"checksum async-attributes 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "efd3d156917d94862e779f356c5acae312b08fd3121e792c857d7928c8088423" -"checksum async-imap 0.2.0 (git+https://github.com/async-email/async-imap?branch=feat/send)" = "" -"checksum async-native-tls 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9e9e7a929bd34c68a82d58a4de7f86fffdaf97fb2af850162a7bb19dd7269b33" -"checksum async-smtp 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb010dac8f81ceb798b089c522766c0427b54253789194b5c7de9720aeb7f091" -"checksum async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "538ecb01eb64eecd772087e5b6f7540cbc917f047727339a472dafed2185b267" -"checksum async-std 1.6.0-beta.1 (git+https://github.com/async-rs/async-std)" = "" -"checksum async-task 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0ac2c016b079e771204030951c366db398864f5026f84a44dafb0ff20f02085d" -"checksum async-task 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c17772156ef2829aadc587461c7753af20b7e8db1529bc66855add962a3b35d3" -"checksum async-trait 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "26c4f3195085c36ea8d24d32b2f828d23296a9370a28aa39d111f6f16bef9f3b" -"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" -"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" -"checksum backtrace 0.3.48 (registry+https://github.com/rust-lang/crates.io-index)" = "0df2f85c8a2abbe3b7d7e748052fdd9b76a0458fdeb16ad4223f5eca78c7c130" -"checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" -"checksum base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" -"checksum bit-set 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" -"checksum bit-vec 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5f0dc55f2d8a1a85650ac47858bb001b4c0dd73d79e3c455a842925e68d29cd3" -"checksum bitfield 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)" = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" -"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" -"checksum blake2b_simd 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a" -"checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" -"checksum block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1c924d49bd09e7c06003acda26cd9742e796e34282ec6c1189404dee0c1f4774" -"checksum block-modes 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "31aa8410095e39fdb732909fb5730a48d5bd7c2e3cd76bd1b07b3dbea130c529" -"checksum block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" -"checksum blowfish 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6aeb80d00f2688459b8542068abd974cfb101e7a82182414a99b5026c0d85cc3" -"checksum broadcaster 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9c972e21e0d055a36cf73e4daae870941fe7a8abcd5ac3396aab9e4c126bd87" -"checksum buf_redux 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" -"checksum bufstream 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8" -"checksum bumpalo 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5356f1d23ee24a1f785a56d1d1a5f0fd5b0f6a0c0fb2412ce11da71649ab78f6" -"checksum byte-pool 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9342e102eac8b1879fbedf9a7e0572c40b0cc5805b663c4d4ca791cae0bae221" -"checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" -"checksum bytecount 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b92204551573580e078dc80017f36a213eb77a0450e4ddd8cfa0f3f2d1f0178f" -"checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" -"checksum bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" -"checksum cargo_metadata 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e5d1b4d380e1bab994591a24c2bdd1b054f64b60bef483a8c598c7c345bc3bbe" -"checksum cast5 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ce5759b4c52ca74f9a98421817c882f1fd9b0071ae41cd61ab9f9d059c04fd6" -"checksum cc 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)" = "404b1fe4f65288577753b17e3b36a04596ee784493ec249bf81c7f2d2acd751c" -"checksum cfb-mode 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "190e7b55d3a27cf8879becf61035a141cbc783f3258a41d16d1706719f991345" -"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" -"checksum charset 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4f426e64df1c3de26cbf44593c6ffff5dbfd43bbf9de0d075058558126b3fc73" -"checksum chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" -"checksum circular 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b0fc239e0f6cb375d2402d48afb92f76f5404fd1df208a41930ec81eda078bea" -"checksum clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "97276801e127ffb46b66ce23f35cc96bd454fa311294bced4bbace7baa8b1d17" -"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -"checksum color_quant 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0dbbb57365263e881e805dc77d94697c9118fd94d8da011240555aa7b23445bd" -"checksum constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" -"checksum core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" -"checksum core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" -"checksum crc24 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "fd121741cf3eb82c08dd3023eb55bf2665e5f60ec20f89760cf836ae4562e6a0" -"checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" -"checksum crossbeam 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "69323bff1fb41c635347b8ead484a5ca6c3f11914d784170b158d8449ab07f8e" -"checksum crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061" -"checksum crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" -"checksum crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" -"checksum crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db" -"checksum crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" -"checksum ctor 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "cf6b25ee9ac1995c54d7adb2eff8cfffb7260bc774fb63c601ec65467f43cd9d" -"checksum curve25519-dalek 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "26778518a7f6cffa1d25a44b602b62b979bd88adb9e99ffec546998cf3404839" -"checksum darling 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" -"checksum darling_core 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" -"checksum darling_macro 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" -"checksum debug_stub_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "496b7f8a2f853313c3ca370641d7ff3e42c32974fdccda8f0684599ed0a3ff6b" -"checksum deflate 0.7.20 (registry+https://github.com/rust-lang/crates.io-index)" = "707b6a7b384888a70c8d2e8650b3e60170dfc6a67bb4aa67b6dfca57af4bedb4" -"checksum derive_builder 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a2658621297f2cf68762a6f7dc0bb7e1ff2cfd6583daef8ee0fed6f7ec468ec0" -"checksum derive_builder_core 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef" -"checksum des 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "74ba5f1b5aee9772379c2670ba81306e65a93c0ee3caade7a1d22b188d88a3af" -"checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" -"checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" -"checksum dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" -"checksum dtoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3" -"checksum ed25519-dalek 1.0.0-pre.3 (registry+https://github.com/rust-lang/crates.io-index)" = "978710b352437433c97b2bff193f2fb1dfd58a093f863dd95e225a19baa599a2" -"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" -"checksum email 0.0.21 (git+https://github.com/deltachat/rust-email)" = "" -"checksum encoded-words 0.1.0 (git+https://github.com/async-email/encoded-words)" = "" -"checksum encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec" -"checksum encoding-index-japanese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91" -"checksum encoding-index-korean 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81" -"checksum encoding-index-simpchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7" -"checksum encoding-index-singlebyte 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a" -"checksum encoding-index-tradchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18" -"checksum encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" -"checksum encoding_rs 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)" = "e8ac63f94732332f44fe654443c46f6375d1939684c17b0afb6cb56b0456e171" -"checksum entities 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca" -"checksum env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3" -"checksum error-chain 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d371106cc88ffdfb1eabd7111e432da544f16f3e2d7bf1dfe8bf575f1df045cd" -"checksum escaper 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "39da344028c2227132b2dfa7c186e2104ecc153467583d00ed9c398f9ff693b0" -"checksum failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" -"checksum failure_derive 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" -"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" -"checksum fallible-iterator 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" -"checksum fallible-streaming-iterator 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" -"checksum fast_chemail 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)" = "495a39d30d624c2caabe6312bfead73e7717692b44e0b32df168c275a2e8e9e4" -"checksum flate2 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "2cfff41391129e0a856d6d822600b8d71179d46879e310417eb9c762eb178b42" -"checksum fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -"checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" -"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" -"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" -"checksum futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1e05b85ec287aac0dc34db7d4a569323df697f9c55b99b15d6b4ef8cde49f613" -"checksum futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5" -"checksum futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399" -"checksum futures-executor 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "10d6bb888be1153d3abeb9006b11b02cf5e9b209fda28693c31ae1e4e012e314" -"checksum futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789" -"checksum futures-macro 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39" -"checksum futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc" -"checksum futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626" -"checksum futures-timer 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a1de7508b218029b0f01662ed8f61b1c964b3ae99d6f25462d0f55a595109df6" -"checksum futures-timer 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" -"checksum futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6" -"checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" -"checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" -"checksum gif 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)" = "471d90201b3b223f3451cd4ad53e34295f16a1df17b1edf3736d47761c3981af" -"checksum gimli 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bcc8e0c9bce37868955864dbecd2b1ab2bdf967e6f28066d65aaac620444b65c" -"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" -"checksum gloo-timers 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f" -"checksum h2 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "79b7246d7e4b979c03fa093da39cfb3617a96bbeee6310af63991668d7e843ff" -"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" -"checksum hermit-abi 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71" -"checksum hex 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" -"checksum hostname 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "21ceb46a83a85e824ef93669c8b390009623863b5c195d1ba747292c0c72f94e" -"checksum http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9" -"checksum http-body 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" -"checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" -"checksum human-panic 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "39f357a500abcbd7c5f967c1d45c8838585b36743823b9d43488f24850534e36" -"checksum humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" -"checksum hyper 0.13.5 (registry+https://github.com/rust-lang/crates.io-index)" = "96816e1d921eca64d208a85aab4f7798455a8e34229ee5a88c935bdee1b78b14" -"checksum hyper-tls 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3adcd308402b9553630734e9c36b77a7e48b3821251ca2493e8cd596763aafaa" -"checksum ident_case 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -"checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" -"checksum image 0.22.5 (registry+https://github.com/rust-lang/crates.io-index)" = "08ed2ada878397b045454ac7cfb011d73132c59f31a955d230bd1f1c2e68eb4a" -"checksum image-meta 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1a2da7b4225d0954c9b8ba1a0dcec85be29f496cba4d85f9390426f810e3ab0d" -"checksum imap-proto 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "16a6def1d5ac8975d70b3fd101d57953fe3278ef2ee5d7816cba54b1d1dfc22f" -"checksum indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292" -"checksum inflate 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1cdb29978cc5797bd8dcc8e5bf7de604891df2a8dc576973d71a281e916db2ff" -"checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" -"checksum itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" -"checksum itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" -"checksum jpeg-decoder 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)" = "5b47b4c4e017b01abdc5bcc126d2d1002e5a75bbe3ce73f9f4f311a916363704" -"checksum js-sys 0.3.39 (registry+https://github.com/rust-lang/crates.io-index)" = "fa5a448de267e7358beaf4a5d849518fe9a0c13fce7afd44b06e68550e5562a7" -"checksum keccak 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" -"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -"checksum kv-log-macro 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "2a2d3beed37e5483887d81eb39de6de03a8346531410e1306ca48a9a89bd3a51" -"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -"checksum lettre 0.9.2 (git+https://github.com/deltachat/lettre)" = "" -"checksum lettre_email 0.9.2 (git+https://github.com/deltachat/lettre)" = "" -"checksum lexical-core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d7043aa5c05dd34fb73b47acb8c3708eac428de4545ea3682ed2f11293ebd890" -"checksum libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)" = "3baa92041a6fec78c687fa0cc2b3fae8884f743d672cf551bed1d6dac6988d0f" -"checksum libm 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" -"checksum libsqlite3-sys 0.17.3 (registry+https://github.com/rust-lang/crates.io-index)" = "56d90181c2904c287e5390186be820e5ef311a3c62edebb7d6ca3d6a48ce041d" -"checksum linked-hash-map 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" -"checksum lock_api 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" -"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" -"checksum lru-cache 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" -"checksum lzw 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084" -"checksum mailparse 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b6435afa64615b3c3efa09cb4066f42dcd236902f30ab48acd36ea59821bb4e0" -"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" -"checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" -"checksum md-5 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a18af3dcaf2b0219366cdb4e2af65a6101457b415c3d1a5c71dd9c2b7c77b9c8" -"checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" -"checksum memoffset 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b4fc2c02a7e374099d4ee95a193111f72d2110197fe200272371758f6c3643d8" -"checksum mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" -"checksum mime_guess 2.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" -"checksum miniz_oxide 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "aa679ff6578b1cddee93d7e82e263b94a575e0bfced07284eb0c037c1d2416a5" -"checksum mio 0.6.22 (registry+https://github.com/rust-lang/crates.io-index)" = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" -"checksum mio-uds 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" -"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" -"checksum native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "2b0d88c06fe90d5ee94048ba40409ef1d9315d86f6f38c2efdaad4fb50c58b2d" -"checksum net2 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)" = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7" -"checksum nix 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4dbdc256eaac2e3bd236d93ad999d3479ef775c863dbda3068c4006a92eec51b" -"checksum nix 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" -"checksum nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" -"checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" -"checksum nom 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b471253da97532da4b61552249c521e01e736071f71c1a4f7ebbfbf0a06aad6" -"checksum num-bigint-dig 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3d03c330f9f7a2c19e3c0b42698e48141d0809c78cd9b6219f85bd7d7e892aa" -"checksum num-derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0c8b15b261814f992e33760b1fca9fe8b693d8a65299f20c9901688636cfb746" -"checksum num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" -"checksum num-iter 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "dfb0800a0291891dd9f4fe7bd9c19384f98f7fbe0cd0f39a2c6b88b9868bbc00" -"checksum num-rational 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" -"checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" -"checksum num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" -"checksum object 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9cbca9424c482ee628fa549d9c812e2cd22f1180b9222c9200fdfa6eb31aecb2" -"checksum once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" -"checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" -"checksum openssl 0.10.29 (registry+https://github.com/rust-lang/crates.io-index)" = "cee6d85f4cb4c4f59a6a85d5b68a233d280c82e29e822913b9c8b129fbf20bdd" -"checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" -"checksum openssl-src 111.9.0+1.1.1g (registry+https://github.com/rust-lang/crates.io-index)" = "a2dbe10ddd1eb335aba3780eb2eaa13e1b7b441d2562fd962398740927f39ec4" -"checksum openssl-sys 0.9.56 (registry+https://github.com/rust-lang/crates.io-index)" = "f02309a7f127000ed50594f0b50ecc69e7c654e16d41b4e8156d1b3df8e0b52e" -"checksum os_type 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7edc011af0ae98b7f88cf7e4a83b70a54a75d2b8cb013d6efd02e5956207e9eb" -"checksum output_vt100 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" -"checksum packed_simd 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a85ea9fc0d4ac0deb6fe7911d38786b32fc11119afd9e9d38b84ff691ce64220" -"checksum parking_lot 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" -"checksum parking_lot_core 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" -"checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" -"checksum pgp 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8172973101790c866e66966002bf1028d0df27bf6b3b29be86a6fd440d8a4285" -"checksum pin-project 0.4.17 (registry+https://github.com/rust-lang/crates.io-index)" = "edc93aeee735e60ecb40cf740eb319ff23eab1c5748abfdb5c180e4ce49f7791" -"checksum pin-project-internal 0.4.17 (registry+https://github.com/rust-lang/crates.io-index)" = "e58db2081ba5b4c93bd6be09c40fd36cb9193a8336c384f3b40012e531aa7e40" -"checksum pin-project-lite 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f7505eeebd78492e0f6108f7171c4948dbb120ee8119d9d77d0afa5469bef67f" -"checksum pin-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -"checksum piper 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c6d62a6ea407d82215154475927b288219b79c8670e3371166210328e758ebaa" -"checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" -"checksum png 0.15.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ef859a23054bbfee7811284275ae522f0434a3c8e7f4b74bd4a35ae7e1c4a283" -"checksum ppv-lite86 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" -"checksum pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427" -"checksum pretty_env_logger 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "717ee476b1690853d222af4634056d830b5197ffd747726a9a1eee6da9f49074" -"checksum proc-macro-hack 0.5.15 (registry+https://github.com/rust-lang/crates.io-index)" = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63" -"checksum proc-macro-nested 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694" -"checksum proc-macro2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)" = "53f5ffe53a6b28e37c9c1ce74893477864d64f74778a93a4beb43c8fa167f639" -"checksum proptest 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)" = "01c477819b845fe023d33583ebf10c9f62518c8d79a0960ba5c36d6ac8a55a5b" -"checksum pulldown-cmark 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "eef52fac62d0ea7b9b4dc7da092aa64ea7ec3d90af6679422d3d7e0e14b6ee15" -"checksum quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" -"checksum quick-xml 0.17.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fe1e430bdcf30c9fdc25053b9c459bb1a4672af4617b6c783d7d91dc17c6bbb0" -"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" -"checksum quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea" -"checksum quoted_printable 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "47b080c5db639b292ac79cbd34be0cfc5d36694768d8341109634d90b86930e2" -"checksum r2d2 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1497e40855348e4a8a40767d8e55174bce1e445a3ac9254ad44ad468ee0485af" -"checksum r2d2_sqlite 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5e15ff794e7c8bb8ae20ccac5bac6a93a4a3af708dd801d4094f80da41196f33" -"checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" -"checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" -"checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -"checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" -"checksum rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -"checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" -"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" -"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" -"checksum rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" -"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" -"checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" -"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" -"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" -"checksum redox_users 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431" -"checksum regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692" -"checksum regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)" = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" -"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" -"checksum rental 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8545debe98b2b139fb04cad8618b530e9b07c152d99a5de83c860b877d67847f" -"checksum rental-impl 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "475e68978dc5b743f2f40d8e0a8fdc83f1c5e78cbf4b8fa5e74e73beebc340de" -"checksum reqwest 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)" = "02b81e49ddec5109a9dcfc5f2a317ff53377c915e9ae9d4f2fb50914b85614e2" -"checksum ripemd160 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ad5112e0dbbb87577bfbc56c42450235e3012ce336e29c5befd7807bd626da4a" -"checksum rsa 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6ed8692d8e0ea3baae03f0f32ecfc13a6c6f1f85fcd6d9fdefcdf364e70f4df9" -"checksum rusqlite 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)" = "57edf4c4cea4d7e0fab069acb5da9e8e8e5403c78abc81b1f37d83af02148ea5" -"checksum rust-argon2 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017" -"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" -"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -"checksum rusty-fork 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3dd93264e10c577503e926bd1430193eeb5d21b059148910082245309b424fae" -"checksum rustyline 4.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0f47ea1ceb347d2deae482d655dc8eef4bd82363d3329baffa3818bd76fea48b" -"checksum ryu 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1" -"checksum safemem 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" -"checksum same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -"checksum sanitize-filename 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "23fd0fec94ec480abfd86bb8f4f6c57e0efb36dac5c852add176ea7b04c74801" -"checksum schannel 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)" = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" -"checksum scheduled-thread-pool 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0988d7fdf88d5e5fcf5923a0f1e8ab345f3e98ab4bc6bc45a2d5ff7f7458fbf6" -"checksum scoped-tls-hkt 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c2e9d7eaddb227e8fbaaa71136ae0e1e913ca159b86c7da82f3e8f0044ad3a63" -"checksum scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" -"checksum security-framework 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "64808902d7d99f78eaddd2b4e2509713babc3dc3c85ad6f4c447680f3c01e535" -"checksum security-framework-sys 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "17bf11d99252f512695eb468de5516e5cf75455521e69dfe343f3b74e4748405" -"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -"checksum send_wrapper 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" -"checksum serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)" = "99e7b308464d16b56eba9964e4972a3eee817760ab60d88c3f86e1fecb08204c" -"checksum serde_derive 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)" = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984" -"checksum serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)" = "993948e75b189211a9b31a7528f950c6adc21f9720b6438ff80a7fa2f864cea2" -"checksum serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97" -"checksum sha-1 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" -"checksum sha2 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "27044adfd2e1f077f649f59deb9490d3941d674002f7d062870a60ebe9bd47a0" -"checksum sha3 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd26bc0e7a2e3a7c959bc494caf58b72ee0c71d67704e9520f736ca7e4853ecf" -"checksum skeptic 0.13.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6fb8ed853fdc19ce09752d63f3a2e5b5158aeb261520cd75eb618bd60305165" -"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" -"checksum smallvec 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" -"checksum smol 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7b1014c048f35c553bfd0155079a62ab9a0e220f9e39f49244c1d58f825ab84a" -"checksum socket2 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)" = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918" -"checksum spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" -"checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" -"checksum static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3" -"checksum stop-token 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "06855fb7c94d3be9b3a57c4d82dfc8a43bb658fbb3b1dda79de89e748d9eb9dd" -"checksum stream-cipher 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8131256a5896cabcf5eb04f4d6dacbe1aefda854b0d9896e09cb58829ec5638c" -"checksum strsim 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" -"checksum strum 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6138f8f88a16d90134763314e3fc76fa3ed6a7db4725d6acf9a3ef95a3188d22" -"checksum strum_macros 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0054a7df764039a6cd8592b9de84be4bec368ff081d203a7d5371cbfa8e65c81" -"checksum subtle 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c65d530b10ccaeac294f349038a597e435b18fb456aadd0840a623f83b9e941" -"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" -"checksum syn 1.0.22 (registry+https://github.com/rust-lang/crates.io-index)" = "1425de3c33b0941002740a420b1a906a350b88d08b82b2c8a01035a3f9447bac" -"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" -"checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" -"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" -"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" -"checksum termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" -"checksum thiserror 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)" = "5976891d6950b4f68477850b5b9e5aa64d955961466f9e174363f573e54e8ca7" -"checksum thiserror-impl 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)" = "ab81dbd1cd69cd2ce22ecfbdd3bdb73334ba25350649408cc6c085f46d89573d" -"checksum thread-local-object 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7da3caa820d0308c84c8654f6cafd81cc3195d45433311cbe22fcf44fc8be071" -"checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" -"checksum time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" -"checksum tokio 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)" = "d099fa27b9702bed751524694adbe393e18b36b204da91eb1cbbbbb4a5ee2d58" -"checksum tokio-tls 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9a70f4fcd7b3b24fb194f837560168208f669ca8cb70d0c4b862944452396343" -"checksum tokio-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" -"checksum toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" -"checksum tower-service 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860" -"checksum traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" -"checksum try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" -"checksum try_from 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "283d3b89e1368717881a9d51dad843cc435380d8109c9e47d38780a324698d8b" -"checksum twofish 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712d261e83e727c8e2dbb75dacac67c36e35db36a958ee504f2164fc052434e1" -"checksum typenum 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" -"checksum unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" -"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" -"checksum unicode-normalization 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4" -"checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" -"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" -"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" -"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" -"checksum unsafe-any 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f30360d7979f5e9c6e6cea48af192ea8fab4afb3cf72597154b8f08935bc9c7f" -"checksum url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" -"checksum utf8parse 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8772a4ccbb4e89959023bc5b7cb8623a795caa7092d99f3aa9501b9484d4557d" -"checksum uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" -"checksum vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" -"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" -"checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" -"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" -"checksum wait-timeout 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" -"checksum walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" -"checksum want 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" -"checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" -"checksum wasm-bindgen 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "e3c7d40d09cdbf0f4895ae58cf57d92e1e57a9dd8ed2e8390514b54a47cc5551" -"checksum wasm-bindgen-backend 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "c3972e137ebf830900db522d6c8fd74d1900dcfc733462e9a12e942b00b4ac94" -"checksum wasm-bindgen-futures 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "8a369c5e1dfb7569e14d62af4da642a3cbc2f9a3652fe586e26ac22222aa4b04" -"checksum wasm-bindgen-macro 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "2cd85aa2c579e8892442954685f0d801f9129de24fa2136b2c6a539c76b65776" -"checksum wasm-bindgen-macro-support 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "8eb197bd3a47553334907ffd2f16507b4f4f01bbec3ac921a7719e0decdfe72a" -"checksum wasm-bindgen-shared 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "a91c2916119c17a8e316507afaaa2dd94b47646048014bbdf6bef098c1bb58ad" -"checksum web-sys 0.3.39 (registry+https://github.com/rust-lang/crates.io-index)" = "8bc359e5dd3b46cb9687a051d50a2fdd228e4ba7cf6fcf861a5365c3d671a642" -"checksum wepoll-binding 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7511014d92edaa2cba607fd6dc62974915bebc3e16f91f911a4bb240b4a3a68c" -"checksum wepoll-sys 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9082a777aed991f6769e2b654aa0cb29f1c3d615daf009829b07b66c7aff6a24" -"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" -"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" -"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" -"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -"checksum winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -"checksum winreg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" -"checksum winutil 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7daf138b6b14196e3830a588acf1e86966c694d3e8fb026fb105b8b5dca07e6e" -"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" -"checksum x25519-dalek 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "637ff90c9540fa3073bb577e65033069e4bae7c79d49d74aa3ffdf5342a53217" -"checksum zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3cbac2ed2ba24cc90f5e06485ac8c7c1e5449fe8911aef4d8877218af021a5b8" -"checksum zeroize_derive 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de251eec69fc7c1bc3923403d18ececb929380e016afe103da75f396704f8ca2" diff --git a/Cargo.toml b/Cargo.toml index 411d7b818..8b59a2df1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,12 +20,12 @@ smallvec = "1.0.0" reqwest = { version = "0.10.0", features = ["blocking", "json"] } num-derive = "0.3.0" num-traits = "0.2.6" -async-smtp = "0.3" +async-smtp = { git = "https://github.com/async-email/async-smtp", version = "0.3" } email = { git = "https://github.com/deltachat/rust-email", branch = "master" } lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" } -async-imap = { git = "https://github.com/async-email/async-imap", branch = "feat/send" } -async-native-tls = "0.3.1" -async-std = { git = "https://github.com/async-rs/async-std", features = ["unstable"] } +async-imap = { git = "https://github.com/async-email/async-imap" } +async-native-tls = { git = "https://github.com/async-email/async-native-tls", version = "0.3.1" } +async-std = { version = "1.6.0-beta.2", features = ["unstable"] } base64 = "0.11" charset = "0.1" percent-encoding = "2.0" @@ -50,7 +50,7 @@ escaper = "0.1.0" bitflags = "1.1.0" debug_stub_derive = "0.3.0" sanitize-filename = "0.2.1" -stop-token = { version = "0.1.1", features = ["unstable"] } +stop-token = { git = "https://github.com/dignifiedquire/stop-token", version = "0.1.1", features = ["unstable"] } mailparse = "0.12.0" encoded-words = { git = "https://github.com/async-email/encoded-words", branch="master" } native-tls = "0.2.3" @@ -71,7 +71,7 @@ tempfile = "3.0" pretty_assertions = "0.6.1" pretty_env_logger = "0.3.0" proptest = "0.9.4" -async-std = { git = "https://github.com/async-rs/async-std", features = ["unstable", "attributes"] } +async-std = { version = "1.6.0-beta.2", features = ["unstable", "attributes"] } [workspace] members = [ diff --git a/deltachat-ffi/Cargo.toml b/deltachat-ffi/Cargo.toml index f7bbcb436..a49ec858f 100644 --- a/deltachat-ffi/Cargo.toml +++ b/deltachat-ffi/Cargo.toml @@ -20,7 +20,7 @@ libc = "0.2" human-panic = "1.0.1" num-traits = "0.2.6" serde_json = "1.0" -async-std = { git = "https://github.com/async-rs/async-std" } +async-std = "1.6.0-beta.2" anyhow = "1.0.28" thiserror = "1.0.14" @@ -28,3 +28,4 @@ thiserror = "1.0.14" default = ["vendored", "nightly"] vendored = ["deltachat/vendored"] nightly = ["deltachat/nightly"] + diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 7dfa6b6bb..e4cf12633 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -1875,7 +1875,6 @@ pub unsafe extern "C" fn dc_imex( } }; - // TODO: this is now blocking, figure out if that is okay let ffi_context = &*context; let param1 = to_opt_string_lossy(param1); diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index 049fe61aa..efe414858 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -587,6 +587,9 @@ class Account(object): def stop_scheduler(self): """ stop core scheduler if it is running. """ + self.ac_log_line("stop_ongoing") + self.stop_ongoing() + self.ac_log_line("context_shutdown (stop core scheduler)") self.stop_ongoing() lib.dc_context_shutdown(self._dc_context) diff --git a/src/context.rs b/src/context.rs index 29c05d02b..c3b614e16 100644 --- a/src/context.rs +++ b/src/context.rs @@ -138,12 +138,10 @@ impl Context { } pub async fn run(&self) { - if self.is_running().await { - return; - } + assert!(!self.is_running().await, "context is already running"); let l = &mut *self.inner.scheduler.write().await; - l.run(self.clone()); + l.run(self.clone()).await; } pub async fn is_running(&self) -> bool { @@ -153,6 +151,7 @@ impl Context { pub async fn stop(&self) { info!(self, "stopping context"); self.inner.stop().await; + info!(self, "stopped context"); } /// Returns a reference to the underlying SQL instance. @@ -499,15 +498,14 @@ impl InnerContext { } async fn stop(&self) { - if self.is_running().await { - let token = { - let lock = &*self.scheduler.read().await; - lock.pre_stop().await - }; - { - let lock = &mut *self.scheduler.write().await; - lock.stop(token).await; - } + assert!(self.is_running().await, "context is already stopped"); + let token = { + let lock = &*self.scheduler.read().await; + lock.pre_stop().await + }; + { + let lock = &mut *self.scheduler.write().await; + lock.stop(token).await; } } } diff --git a/src/scheduler.rs b/src/scheduler.rs index d0af4347f..b9e98a43d 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -17,9 +17,13 @@ pub(crate) enum Scheduler { Stopped, Running { inbox: ImapConnectionState, + inbox_handle: Option>, mvbox: ImapConnectionState, + mvbox_handle: Option>, sentbox: ImapConnectionState, + sentbox_handle: Option>, smtp: SmtpConnectionState, + smtp_handle: Option>, probe_network: bool, }, } @@ -47,7 +51,7 @@ impl Context { } } -async fn inbox_loop(ctx: Context, inbox_handlers: ImapConnectionHandlers) { +async fn inbox_loop(ctx: Context, started: Sender<()>, inbox_handlers: ImapConnectionHandlers) { use futures::future::FutureExt; info!(ctx, "starting inbox loop"); @@ -59,6 +63,7 @@ async fn inbox_loop(ctx: Context, inbox_handlers: ImapConnectionHandlers) { let ctx1 = ctx.clone(); let fut = async move { + started.send(()).await; let ctx = ctx1; if let Err(err) = connection.connect_configured(&ctx).await { error!(ctx, "{}", err); @@ -92,8 +97,13 @@ async fn inbox_loop(ctx: Context, inbox_handlers: ImapConnectionHandlers) { } }; - info!(ctx, "Shutting down inbox loop"); - fut.race(stop_receiver.recv().map(|_| ())).await; + stop_receiver + .recv() + .map(|_| { + info!(ctx, "shutting down inbox loop"); + }) + .race(fut) + .await; shutdown_sender.send(()).await; } @@ -147,6 +157,7 @@ async fn fetch_idle(ctx: &Context, connection: &mut Imap) { async fn simple_imap_loop( ctx: Context, + started: Sender<()>, inbox_handlers: ImapConnectionHandlers, folder: impl AsRef, ) { @@ -159,7 +170,11 @@ async fn simple_imap_loop( shutdown_sender, } = inbox_handlers; + let ctx1 = ctx.clone(); + let fut = async move { + started.send(()).await; + let ctx = ctx1; if let Err(err) = connection.connect_configured(&ctx).await { error!(ctx, "{}", err); return; @@ -200,11 +215,19 @@ async fn simple_imap_loop( } }; - fut.race(stop_receiver.recv().map(|_| ())).await; + stop_receiver + .recv() + .map(|_| { + info!(ctx, "shutting down simple loop"); + }) + .race(fut) + .await; shutdown_sender.send(()).await; } -async fn smtp_loop(ctx: Context, smtp_handlers: SmtpConnectionHandlers) { +async fn smtp_loop(ctx: Context, started: Sender<()>, smtp_handlers: SmtpConnectionHandlers) { + use futures::future::FutureExt; + info!(ctx, "starting smtp loop"); let SmtpConnectionHandlers { mut connection, @@ -213,7 +236,10 @@ async fn smtp_loop(ctx: Context, smtp_handlers: SmtpConnectionHandlers) { idle_interrupt_receiver, } = smtp_handlers; + let ctx1 = ctx.clone(); let fut = async move { + started.send(()).await; + let ctx = ctx1; loop { let probe_network = ctx.scheduler.read().await.get_probe_network(); match job::load_next(&ctx, Thread::Smtp, probe_network) @@ -225,8 +251,6 @@ async fn smtp_loop(ctx: Context, smtp_handlers: SmtpConnectionHandlers) { ctx.scheduler.write().await.set_probe_network(false); } Ok(None) | Err(async_std::future::TimeoutError { .. }) => { - use futures::future::FutureExt; - // Fake Idle async_std::task::sleep(Duration::from_millis(500)) .race(idle_interrupt_receiver.recv().map(|_| ())) @@ -236,13 +260,19 @@ async fn smtp_loop(ctx: Context, smtp_handlers: SmtpConnectionHandlers) { } }; - fut.race(stop_receiver.recv()).await.ok(); + stop_receiver + .recv() + .map(|_| { + info!(ctx, "shutting down smtp loop"); + }) + .race(fut) + .await; shutdown_sender.send(()).await; } impl Scheduler { /// Start the scheduler, panics if it is already running. - pub fn run(&mut self, ctx: Context) { + pub async fn run(&mut self, ctx: Context) { let (mvbox, mvbox_handlers) = ImapConnectionState::new(); let (sentbox, sentbox_handlers) = ImapConnectionState::new(); let (smtp, smtp_handlers) = SmtpConnectionState::new(); @@ -254,23 +284,65 @@ impl Scheduler { sentbox, smtp, probe_network: false, + inbox_handle: None, + mvbox_handle: None, + sentbox_handle: None, + smtp_handle: None, }; - let ctx1 = ctx.clone(); - task::spawn(async move { inbox_loop(ctx1, inbox_handlers).await }); + let (inbox_start_send, inbox_start_recv) = channel(1); + if let Scheduler::Running { inbox_handle, .. } = self { + let ctx1 = ctx.clone(); + *inbox_handle = Some(task::spawn(async move { + inbox_loop(ctx1, inbox_start_send, inbox_handlers).await + })); + } - let ctx1 = ctx.clone(); - task::spawn(async move { - simple_imap_loop(ctx1, mvbox_handlers, "configured_mvbox_folder").await - }); + let (mvbox_start_send, mvbox_start_recv) = channel(1); + if let Scheduler::Running { mvbox_handle, .. } = self { + let ctx1 = ctx.clone(); + *mvbox_handle = Some(task::spawn(async move { + simple_imap_loop( + ctx1, + mvbox_start_send, + mvbox_handlers, + "configured_mvbox_folder", + ) + .await + })); + } - let ctx1 = ctx.clone(); - task::spawn(async move { - simple_imap_loop(ctx1, sentbox_handlers, "configured_sentbox_folder").await - }); + let (sentbox_start_send, sentbox_start_recv) = channel(1); + if let Scheduler::Running { sentbox_handle, .. } = self { + let ctx1 = ctx.clone(); + *sentbox_handle = Some(task::spawn(async move { + simple_imap_loop( + ctx1, + sentbox_start_send, + sentbox_handlers, + "configured_sentbox_folder", + ) + .await + })); + } - let ctx1 = ctx.clone(); - task::spawn(async move { smtp_loop(ctx1, smtp_handlers).await }); + let (smtp_start_send, smtp_start_recv) = channel(1); + if let Scheduler::Running { smtp_handle, .. } = self { + let ctx1 = ctx.clone(); + *smtp_handle = Some(task::spawn(async move { + smtp_loop(ctx1, smtp_start_send, smtp_handlers).await + })); + } + + // wait for all loops to be started + inbox_start_recv + .recv() + .try_join(mvbox_start_recv.recv()) + .try_join(sentbox_start_recv.recv()) + .try_join(smtp_start_recv.recv()) + .await + .map(|_| ()) + .unwrap_or_else(|err| error!(ctx, "failed to start scheduler: {}", err)); info!(ctx, "scheduler is running"); } @@ -365,7 +437,18 @@ impl Scheduler { Scheduler::Stopped => { panic!("WARN: already stopped"); } - Scheduler::Running { .. } => { + Scheduler::Running { + inbox_handle, + mvbox_handle, + sentbox_handle, + smtp_handle, + .. + } => { + inbox_handle.take().expect("inbox not started").await; + mvbox_handle.take().expect("mvbox not started").await; + sentbox_handle.take().expect("sentbox not started").await; + smtp_handle.take().expect("smtp not started").await; + *self = Scheduler::Stopped; } } From 9e43540dfa8ed9284428e6e67ba48739fb293cb8 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Wed, 20 May 2020 17:05:00 +0200 Subject: [PATCH 056/118] fix docs, some renames --- python/src/deltachat/account.py | 30 +++++++++++++++--------------- python/src/deltachat/events.py | 11 ++++------- python/x.py | 9 +++++++-- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index efe414858..416b6a7db 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -15,7 +15,7 @@ from .message import Message from .contact import Contact from .tracker import ImexTracker, ConfigureTracker from . import hookspec -from .events import FFIEventLogger, CallbackThread +from .events import FFIEventLogger, EventThread class MissingCredentials(ValueError): @@ -59,7 +59,7 @@ class Account(object): db_path = db_path.encode("utf8") if not lib.dc_open(self._dc_context, db_path, ffi.NULL): raise ValueError("Could not dc_open: {}".format(db_path)) - self._cb_thread = CallbackThread(self) + self._event_thread = EventThread(self) self._configkeys = self.get_config("sys.config_keys").split() atexit.register(self.shutdown) hook.dc_account_init(account=self) @@ -462,7 +462,7 @@ class Account(object): If sending out was unsuccessful, a RuntimeError is raised. """ self.check_is_configured() - if not self._cb_thread.is_alive() or not self.is_started(): + if not self._event_thread.is_alive() or not self.is_started(): raise RuntimeError("IO not running, can not send out") res = lib.dc_initiate_key_transfer(self._dc_context) if res == ffi.NULL: @@ -557,18 +557,18 @@ class Account(object): lib.dc_stop_ongoing_process(self._dc_context) def start(self): - """ start this account (activate imap/smtp threads etc.) - and return immediately. + """ start this account's IO scheduling (Rust-core async scheduler) - If this account is not configured, an internal configuration - job will be scheduled if config values are sufficiently specified. + If this account is not configured but "addr" and "mail_pw" config + values are set, dc_configure() will be called. You may call `wait_shutdown` or `shutdown` after the - account is in started mode. + account is started. :raises MissingCredentials: if `addr` and `mail_pw` values are not set. + :raises ConfigureFailed: if the account could not be configured. - :returns: None + :returns: None (account is configured and with io-scheduling running) """ if not self.is_configured(): if not self.get_config("addr") or not self.get_config("mail_pw"): @@ -579,7 +579,7 @@ class Account(object): lib.dc_context_run(self._dc_context) def is_started(self): - return bool(lib.dc_is_running(self._dc_context)) + return self._event_thread.is_alive() and bool(lib.dc_is_running(self._dc_context)) def wait_shutdown(self): """ wait until shutdown of this account has completed. """ @@ -589,21 +589,21 @@ class Account(object): """ stop core scheduler if it is running. """ self.ac_log_line("stop_ongoing") self.stop_ongoing() - + self.ac_log_line("context_shutdown (stop core scheduler)") self.stop_ongoing() lib.dc_context_shutdown(self._dc_context) def shutdown(self, wait=True): - """ shutdown account, stop threads and close and remove + """ shutdown and destroy account (stop callback thread, close and remove underlying dc_context.""" dc_context = self._dc_context if dc_context is None: return - if self._cb_thread.is_alive(): + if self._event_thread.is_alive(): self.ac_log_line("stop threads") - self._cb_thread.stop(wait=False) + self._event_thread.stop(wait=False) self.stop_scheduler() @@ -611,7 +611,7 @@ class Account(object): lib.dc_close(dc_context) self.ac_log_line("wait threads for real") if wait: - self._cb_thread.stop(wait=wait) + self._event_thread.stop(wait=wait) self._dc_context = None atexit.unregister(self.shutdown) self._shutdown_event.set() diff --git a/python/src/deltachat/events.py b/python/src/deltachat/events.py index 4a7f7e542..851fcc2a0 100644 --- a/python/src/deltachat/events.py +++ b/python/src/deltachat/events.py @@ -124,8 +124,8 @@ class FFIEventTracker: return self.account.get_message_by_id(ev.data2) -class CallbackThread(threading.Thread): - """ Callback Thread for an account. +class EventThread(threading.Thread): + """ Event Thread for an account. With each Account init this callback thread is started. """ @@ -133,7 +133,7 @@ class CallbackThread(threading.Thread): self.account = account self._dc_context = account._dc_context self._thread_quitflag = False - super(CallbackThread, self).__init__(name="callback") + super(EventThread, self).__init__(name="events") self.start() @contextmanager @@ -150,17 +150,14 @@ class CallbackThread(threading.Thread): def run(self): """ get and run events until shutdown. """ - with self.log_execution("CALLBACK THREAD START"): + with self.log_execution("EVENT THREAD"): self._inner_run() def _inner_run(self): while lib.dc_is_open(self._dc_context) and not self._thread_quitflag: - self.account.ac_log_line("waiting for event") event = lib.dc_get_next_event(self._dc_context) if event == ffi.NULL: break - self.account.ac_log_line("got event {}".format(event)) - evt = lib.dc_event_get_id(event) data1 = lib.dc_event_get_data1(event) data2 = lib.dc_event_get_data2(event) diff --git a/python/x.py b/python/x.py index 6ad780bc3..3f7a6cdb1 100644 --- a/python/x.py +++ b/python/x.py @@ -21,11 +21,16 @@ acc.start() # lib.dc_configure + lib.dc_context_run assert acc.is_configured() acc.stop_scheduler() +run = 0 while 1: - print("starting scheduler") + print("****** starting scheduler") acc.start() - print("stopping scheduler") + import time ; time.sleep(0.5) + print("******* stopping scheduler") acc.stop_scheduler() + print("******* waiting", run) + import time ; time.sleep(1.0) + run += 1 contact = acc.create_contact("holger@deltachat.de") chat = acc.create_chat_by_contact(contact) From 1029c63a2077008a1fc1f2c1686fd89e369ec32c Mon Sep 17 00:00:00 2001 From: holger krekel Date: Wed, 20 May 2020 17:21:57 +0200 Subject: [PATCH 057/118] python fixes --- python/src/deltachat/account.py | 8 +++++--- python/src/deltachat/events.py | 9 +++++---- python/tests/test_account.py | 5 ----- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index 416b6a7db..51b73f37e 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -590,9 +590,11 @@ class Account(object): self.ac_log_line("stop_ongoing") self.stop_ongoing() - self.ac_log_line("context_shutdown (stop core scheduler)") - self.stop_ongoing() - lib.dc_context_shutdown(self._dc_context) + if self.is_started(): + self.ac_log_line("context_shutdown (stop core scheduler)") + lib.dc_context_shutdown(self._dc_context) + else: + self.ac_log_line("stop_scheduler called on non-running context") def shutdown(self, wait=True): """ shutdown and destroy account (stop callback thread, close and remove diff --git a/python/src/deltachat/events.py b/python/src/deltachat/events.py index 851fcc2a0..5c3dbf9ff 100644 --- a/python/src/deltachat/events.py +++ b/python/src/deltachat/events.py @@ -194,17 +194,18 @@ class EventThread(threading.Thread): def _map_ffi_event(self, ffi_event): name = ffi_event.name + account = self.account if name == "DC_EVENT_CONFIGURE_PROGRESS": data1 = ffi_event.data1 if data1 == 0 or data1 == 1000: success = data1 == 1000 yield "ac_configure_completed", dict(success=success) elif name == "DC_EVENT_INCOMING_MSG": - msg = self.get_message_by_id(ffi_event.data2) + msg = account.get_message_by_id(ffi_event.data2) yield map_system_message(msg) or ("ac_incoming_message", dict(message=msg)) elif name == "DC_EVENT_MSGS_CHANGED": if ffi_event.data2 != 0: - msg = self.account.get_message_by_id(ffi_event.data2) + msg = account.get_message_by_id(ffi_event.data2) if msg.is_outgoing(): res = map_system_message(msg) if res and res[0].startswith("ac_member"): @@ -213,8 +214,8 @@ class EventThread(threading.Thread): elif msg.is_in_fresh(): yield map_system_message(msg) or ("ac_incoming_message", dict(message=msg)) elif name == "DC_EVENT_MSG_DELIVERED": - msg = self.account.get_message_by_id(ffi_event.data2) + msg = account.get_message_by_id(ffi_event.data2) yield "ac_message_delivered", dict(message=msg) elif name == "DC_EVENT_CHAT_MODIFIED": - chat = self.account.get_chat_by_id(ffi_event.data1) + chat = account.get_chat_by_id(ffi_event.data1) yield "ac_chat_modified", dict(chat=chat) diff --git a/python/tests/test_account.py b/python/tests/test_account.py index 9855085d2..33f297f44 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -521,11 +521,6 @@ class TestOnlineAccount: ac2.create_chat_by_contact(ac2.create_contact(email=ac1.get_config("addr"))) return chat - def test_double_iter_events(self, acfactory): - ac1 = acfactory.get_one_online_account() - with pytest.raises(RuntimeError): - next(ac1.iter_events()) - @pytest.mark.ignored def test_configure_generate_key(self, acfactory, lp): # A slow test which will generate new keys. From 0ec5b8d6ddc8f39438f685108ec6ca076726fa94 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Wed, 20 May 2020 18:10:13 +0200 Subject: [PATCH 058/118] fix --ignored handling --- python/src/deltachat/testplugin.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/python/src/deltachat/testplugin.py b/python/src/deltachat/testplugin.py index d38788361..4ce70f0f0 100644 --- a/python/src/deltachat/testplugin.py +++ b/python/src/deltachat/testplugin.py @@ -74,6 +74,9 @@ def pytest_configure(config): @pytest.hookimpl(hookwrapper=True) def pytest_runtest_setup(self, item): + if item.get_closest_marker("ignored"): + if not item.config.getvalue("ignored"): + pytest.skip("use --ignored to run this test") self.enable_logging(item) yield self.disable_logging(item) From 16e519430a6b66478c437566577669d4beeeb523 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 20 May 2020 18:38:53 +0200 Subject: [PATCH 059/118] fix(job): avoid double insertion --- src/job.rs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/job.rs b/src/job.rs index 43b795c54..7da9b6186 100644 --- a/src/job.rs +++ b/src/job.rs @@ -1018,23 +1018,8 @@ pub async fn add( return; } - let param_str = param.to_string(); let job = Job::new(action, foreign_id as u32, param, delay_seconds); job.save(context).await; - let timestamp = time(); - let thread: Thread = action.into(); - - context.sql.execute( - "INSERT INTO jobs (added_timestamp, thread, action, foreign_id, param, desired_timestamp) VALUES (?,?,?,?,?,?);", - paramsv![ - timestamp, - thread, - action, - foreign_id, - param_str, - (timestamp + delay_seconds as i64) - ] - ).await.ok(); if delay_seconds == 0 { match action { From 2279e183293bfaef864812b79f7ad4a13c739748 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Wed, 20 May 2020 18:57:51 +0200 Subject: [PATCH 060/118] fix logging and docstrings --- python/src/deltachat/account.py | 18 ++++++++---------- python/src/deltachat/events.py | 16 +++++----------- python/src/deltachat/hookspec.py | 5 +++-- 3 files changed, 16 insertions(+), 23 deletions(-) diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index 51b73f37e..55f8c6d5c 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -15,7 +15,7 @@ from .message import Message from .contact import Contact from .tracker import ImexTracker, ConfigureTracker from . import hookspec -from .events import FFIEventLogger, EventThread +from .events import EventThread class MissingCredentials(ValueError): @@ -41,8 +41,6 @@ class Account(object): self._logging = logging self.add_account_plugin(self) - if logging: - self.add_account_plugin(FFIEventLogger(self, logid=str(id(self))[:5])) self._dc_context = ffi.gc( lib.dc_context_new(ffi.NULL, as_dc_charpointer(os_name)), @@ -75,7 +73,7 @@ class Account(object): # def __del__(self): # self.shutdown() - def ac_log_line(self, msg): + def log(self, msg): if self._logging: self._pm.hook.ac_log_line(message=msg) @@ -587,14 +585,14 @@ class Account(object): def stop_scheduler(self): """ stop core scheduler if it is running. """ - self.ac_log_line("stop_ongoing") + self.log("stop_ongoing") self.stop_ongoing() if self.is_started(): - self.ac_log_line("context_shutdown (stop core scheduler)") + self.log("context_shutdown (stop core scheduler)") lib.dc_context_shutdown(self._dc_context) else: - self.ac_log_line("stop_scheduler called on non-running context") + self.log("stop_scheduler called on non-running context") def shutdown(self, wait=True): """ shutdown and destroy account (stop callback thread, close and remove @@ -604,14 +602,14 @@ class Account(object): return if self._event_thread.is_alive(): - self.ac_log_line("stop threads") + self.log("stop threads") self._event_thread.stop(wait=False) self.stop_scheduler() - self.ac_log_line("dc_close") + self.log("dc_close") lib.dc_close(dc_context) - self.ac_log_line("wait threads for real") + self.log("wait threads for real") if wait: self._event_thread.stop(wait=wait) self._dc_context = None diff --git a/python/src/deltachat/events.py b/python/src/deltachat/events.py index 5c3dbf9ff..9fc50ef56 100644 --- a/python/src/deltachat/events.py +++ b/python/src/deltachat/events.py @@ -38,13 +38,7 @@ class FFIEventLogger: @account_hookimpl def ac_process_ffi_event(self, ffi_event): - self._log_event(ffi_event) - - def _log_event(self, ffi_event): - # don't show events that are anyway empty impls now - if ffi_event.name == "DC_EVENT_GET_STRING": - return - self.account.ac_log_line(str(ffi_event)) + self.account.log(str(ffi_event)) @account_hookimpl def ac_log_line(self, message): @@ -97,7 +91,7 @@ class FFIEventTracker: assert not rex.match(ev.name), "event found {}".format(ev) def get_matching(self, event_name_regex, check_error=True, timeout=None): - self.account.ac_log_line("-- waiting for event with regex: {} --".format(event_name_regex)) + self.account.log("-- waiting for event with regex: {} --".format(event_name_regex)) rex = re.compile("(?:{}).*".format(event_name_regex)) while 1: ev = self.get(timeout=timeout, check_error=check_error) @@ -138,9 +132,9 @@ class EventThread(threading.Thread): @contextmanager def log_execution(self, message): - self.account.ac_log_line(message + " START") + self.account.log(message + " START") yield - self.account.ac_log_line(message + " FINISHED") + self.account.log(message + " FINISHED") def stop(self, wait=False): self._thread_quitflag = True @@ -181,7 +175,7 @@ class EventThread(threading.Thread): ffi_event = FFIEvent(name=evt_name, data1=data1, data2=data2) self.account._pm.hook.ac_process_ffi_event(account=self, ffi_event=ffi_event) for name, kwargs in self._map_ffi_event(ffi_event): - self.account.ac_log_line("calling hook name={} kwargs={}".format(name, kwargs)) + # self.account.log("calling hook name={} kwargs={}".format(name, kwargs)) hook = getattr(self.account._pm.hook, name) try: hook(**kwargs) diff --git a/python/src/deltachat/hookspec.py b/python/src/deltachat/hookspec.py index 00ec36d98..b0e896aaf 100644 --- a/python/src/deltachat/hookspec.py +++ b/python/src/deltachat/hookspec.py @@ -15,8 +15,9 @@ global_hookimpl = pluggy.HookimplMarker(global_spec_name) class PerAccount: """ per-Account-instance hook specifications. - Except for ac_process_ffi_event all hooks are executed - in the thread which calls Account.wait_shutdown(). + All hooks are executed in a dedicated Event thread. + Hooks are not allowed to block/last long as this + blocks overall event processing on the python side. """ @classmethod def _make_plugin_manager(cls): From 2f09bb468e3752cf4c3dffcc35c233edd19f57e3 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Wed, 20 May 2020 19:10:36 +0200 Subject: [PATCH 061/118] fix configure_canceled test --- python/src/deltachat/testplugin.py | 5 +++-- python/src/deltachat/tracker.py | 6 ++++++ python/tests/test_account.py | 13 ++++++++----- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/python/src/deltachat/testplugin.py b/python/src/deltachat/testplugin.py index 4ce70f0f0..adce704d0 100644 --- a/python/src/deltachat/testplugin.py +++ b/python/src/deltachat/testplugin.py @@ -297,7 +297,7 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, data): return ac, dict(configdict) def get_online_configuring_account(self, mvbox=False, sentbox=False, move=False, - pre_generated_key=True, quiet=False, config={}): + pre_generated_key=True, quiet=False, config={}, start=True): ac, configdict = self.get_online_config( pre_generated_key=pre_generated_key, quiet=quiet) configdict.update(config) @@ -305,7 +305,8 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, data): configdict["mvbox_move"] = str(int(move)) configdict["sentbox_watch"] = str(int(sentbox)) ac.update_config(configdict) - ac.start() + if start: + ac.start() return ac def get_one_online_account(self, pre_generated_key=True, mvbox=False, move=False): diff --git a/python/src/deltachat/tracker.py b/python/src/deltachat/tracker.py index 4570f3d4c..250016d6e 100644 --- a/python/src/deltachat/tracker.py +++ b/python/src/deltachat/tracker.py @@ -45,6 +45,7 @@ class ConfigureTracker: self._smtp_finished = Event() self._imap_finished = Event() self._ffi_events = [] + self._progress = Queue() @account_hookimpl def ac_process_ffi_event(self, ffi_event): @@ -53,6 +54,8 @@ class ConfigureTracker: self._smtp_finished.set() elif ffi_event.name == "DC_EVENT_IMAP_CONNECTED": self._imap_finished.set() + elif ffi_event.name == "DC_EVENT_CONFIGURE_PROGRESS": + self._progress.put(ffi_event.data1) @account_hookimpl def ac_configure_completed(self, success): @@ -66,6 +69,9 @@ class ConfigureTracker: """ wait until smtp is configured. """ self._imap_finished.wait() + def wait_progress(self): + return self._progress.get() + def wait_finish(self): """ wait until configure is completed. diff --git a/python/tests/test_account.py b/python/tests/test_account.py index 33f297f44..73e03ba95 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -3,9 +3,10 @@ import pytest import os import queue import time -from deltachat import const, Account +from deltachat import const, Account, capi from deltachat.message import Message from deltachat.hookspec import account_hookimpl +from deltachat.tracker import ConfigureTracker from datetime import datetime, timedelta from conftest import (wait_configuration_progress, wait_securejoin_inviter_progress) @@ -563,10 +564,12 @@ class TestOnlineAccount: assert msg3_in.is_encrypted() def test_configure_canceled(self, acfactory): - ac1 = acfactory.get_online_configuring_account() - wait_configuration_progress(ac1, 200) - ac1.stop_ongoing() - wait_configuration_progress(ac1, 0, 0) + ac1 = acfactory.get_online_configuring_account(start=False) + with ac1.temp_plugin(ConfigureTracker()) as config_tracker: + capi.lib.dc_configure(ac1._dc_context) + config_tracker.wait_progress() + ac1.stop_ongoing() + config_tracker.wait_finish() def test_export_import_self_keys(self, acfactory, tmpdir): ac1, ac2 = acfactory.get_two_online_accounts() From c3458ec59f0b609d2c8651b13c5fd5441d6ddb58 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Wed, 20 May 2020 19:17:48 +0200 Subject: [PATCH 062/118] fix test --- python/tests/test_account.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/python/tests/test_account.py b/python/tests/test_account.py index 73e03ba95..5261d2289 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -573,8 +573,6 @@ class TestOnlineAccount: def test_export_import_self_keys(self, acfactory, tmpdir): ac1, ac2 = acfactory.get_two_online_accounts() - ac1.stop_threads() - ac2.stop_threads() dir = tmpdir.mkdir("exportdir") export_files = ac1.export_self_keys(dir.strpath) From fedc946886d59bdc04ae7cf728d4d46ee3344106 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Wed, 20 May 2020 19:44:06 +0200 Subject: [PATCH 063/118] fix online configure tests --- python/src/deltachat/account.py | 10 ++++++++++ python/src/deltachat/tracker.py | 7 +++++-- python/tests/test_account.py | 18 ++++++++++-------- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index 55f8c6d5c..ff08cc237 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -576,6 +576,16 @@ class Account(object): config_tracker.wait_finish() lib.dc_context_run(self._dc_context) + @contextmanager + def configure(self): + if self.is_configured(): + return + if not self.get_config("addr") or not self.get_config("mail_pw"): + raise MissingCredentials("addr or mail_pwd not set in config") + with self.temp_plugin(ConfigureTracker()) as config_tracker: + lib.dc_configure(self._dc_context) + yield config_tracker + def is_started(self): return self._event_thread.is_alive() and bool(lib.dc_is_running(self._dc_context)) diff --git a/python/src/deltachat/tracker.py b/python/src/deltachat/tracker.py index 250016d6e..a566c5d5d 100644 --- a/python/src/deltachat/tracker.py +++ b/python/src/deltachat/tracker.py @@ -69,8 +69,11 @@ class ConfigureTracker: """ wait until smtp is configured. """ self._imap_finished.wait() - def wait_progress(self): - return self._progress.get() + def wait_progress(self, data1=None): + while 1: + evdata = self._progress.get() + if data1 is None or evdata == data1: + break def wait_finish(self): """ wait until configure is completed. diff --git a/python/tests/test_account.py b/python/tests/test_account.py index 5261d2289..283a619af 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -1211,6 +1211,7 @@ class TestOnlineAccount: qr = chat.get_join_qr() lp.sec("ac2: start QR-code based join-group protocol") ch = ac2.qr_join_chat(qr) + lp.sec("ac2: qr_join_chat() returned") assert ch.id >= 10 # check that at least some of the handshake messages are deleted ac1._evtracker.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED") @@ -1659,26 +1660,27 @@ class TestOnlineConfigureFails: ac1, configdict = acfactory.get_online_config() ac1.update_config(dict(addr=configdict["addr"], mail_pw="123")) - ac1.start() - wait_configuration_progress(ac1, 500) + with ac1.configure() as tracker: + tracker.wait_progress(500) + tracker.wait_progress(0) ev = ac1._evtracker.get_matching("DC_EVENT_ERROR_NETWORK") assert "cannot login" in ev.data2.lower() - wait_configuration_progress(ac1, 0, 0) def test_invalid_user(self, acfactory): ac1, configdict = acfactory.get_online_config() ac1.update_config(dict(addr="x" + configdict["addr"], mail_pw=configdict["mail_pw"])) - ac1.start() - wait_configuration_progress(ac1, 500) + with ac1.configure() as tracker: + tracker.wait_progress(500) + tracker.wait_progress(0) ev = ac1._evtracker.get_matching("DC_EVENT_ERROR_NETWORK") assert "cannot login" in ev.data2.lower() - wait_configuration_progress(ac1, 0, 0) def test_invalid_domain(self, acfactory): ac1, configdict = acfactory.get_online_config() ac1.update_config((dict(addr=configdict["addr"] + "x", mail_pw=configdict["mail_pw"]))) - ac1.start() + with ac1.configure() as tracker: + tracker.wait_progress(500) + tracker.wait_progress(0) wait_configuration_progress(ac1, 500) ev = ac1._evtracker.get_matching("DC_EVENT_ERROR_NETWORK") assert "could not connect" in ev.data2.lower() - wait_configuration_progress(ac1, 0, 0) From ad522cd798c878a6fda28bcac9d90ab640308c57 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Wed, 20 May 2020 21:13:03 +0200 Subject: [PATCH 064/118] simplify and speedup configuration handling refactor conftest.py away --- python/src/deltachat/account.py | 31 ++++++++---- python/src/deltachat/events.py | 7 +++ python/src/deltachat/testplugin.py | 17 +++---- python/tests/conftest.py | 18 ------- python/tests/test_account.py | 80 ++++++++++++++---------------- python/tests/test_increation.py | 30 +++++------ 6 files changed, 83 insertions(+), 100 deletions(-) delete mode 100644 python/tests/conftest.py diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index ff08cc237..0f18fcbe3 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -543,6 +543,10 @@ class Account(object): self._pm.check_pending() return plugin + def remove_account_plugin(self, plugin, name=None): + """ remove an account plugin. """ + self._pm.unregister(plugin, name=name) + @contextmanager def temp_plugin(self, plugin): """ run a with-block with the given plugin temporarily registered. """ @@ -569,22 +573,27 @@ class Account(object): :returns: None (account is configured and with io-scheduling running) """ if not self.is_configured(): - if not self.get_config("addr") or not self.get_config("mail_pw"): - raise MissingCredentials("addr or mail_pwd not set in config") - with self.temp_plugin(ConfigureTracker()) as config_tracker: - lib.dc_configure(self._dc_context) - config_tracker.wait_finish() + self.configure() + self.wait_configure_finish() lib.dc_context_run(self._dc_context) - @contextmanager def configure(self): - if self.is_configured(): - return + assert not self.is_configured() + assert not hasattr(self, "_configtracker") if not self.get_config("addr") or not self.get_config("mail_pw"): raise MissingCredentials("addr or mail_pwd not set in config") - with self.temp_plugin(ConfigureTracker()) as config_tracker: - lib.dc_configure(self._dc_context) - yield config_tracker + if hasattr(self, "_configtracker"): + self.remove_account_plugin(self._configtracker) + self._configtracker = ConfigureTracker() + self.add_account_plugin(self._configtracker) + lib.dc_configure(self._dc_context) + + def wait_configure_finish(self): + try: + self._configtracker.wait_finish() + finally: + self.remove_account_plugin(self._configtracker) + del self._configtracker def is_started(self): return self._event_thread.is_alive() and bool(lib.dc_is_running(self._dc_context)) diff --git a/python/src/deltachat/events.py b/python/src/deltachat/events.py index 9fc50ef56..bd23e3359 100644 --- a/python/src/deltachat/events.py +++ b/python/src/deltachat/events.py @@ -90,6 +90,13 @@ class FFIEventTracker: else: assert not rex.match(ev.name), "event found {}".format(ev) + def wait_securejoin_inviter_progress(self, target): + while 1: + event = self.get_matching("DC_EVENT_SECUREJOIN_INVITER_PROGRESS") + if event.data2 >= target: + print("** SECUREJOINT-INVITER PROGRESS {}".format(target), self.account) + break + def get_matching(self, event_name_regex, check_error=True, timeout=None): self.account.log("-- waiting for event with regex: {} --".format(event_name_regex)) rex = re.compile("(?:{}).*".format(event_name_regex)) diff --git a/python/src/deltachat/testplugin.py b/python/src/deltachat/testplugin.py index adce704d0..910db5c71 100644 --- a/python/src/deltachat/testplugin.py +++ b/python/src/deltachat/testplugin.py @@ -13,7 +13,6 @@ import pytest import requests from . import Account, const -from .tracker import ConfigureTracker from .capi import lib from .events import FFIEventLogger, FFIEventTracker from _pytest.monkeypatch import MonkeyPatch @@ -234,7 +233,6 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, data): def make_account(self, path, logid, quiet=False): ac = Account(path, logging=self._logging) ac._evtracker = ac.add_account_plugin(FFIEventTracker(ac)) - ac._configtracker = ac.add_account_plugin(ConfigureTracker()) if not quiet: ac.add_account_plugin(FFIEventLogger(ac, logid=logid)) self._accounts.append(ac) @@ -297,7 +295,7 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, data): return ac, dict(configdict) def get_online_configuring_account(self, mvbox=False, sentbox=False, move=False, - pre_generated_key=True, quiet=False, config={}, start=True): + pre_generated_key=True, quiet=False, config={}): ac, configdict = self.get_online_config( pre_generated_key=pre_generated_key, quiet=quiet) configdict.update(config) @@ -305,23 +303,20 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, data): configdict["mvbox_move"] = str(int(move)) configdict["sentbox_watch"] = str(int(sentbox)) ac.update_config(configdict) - if start: - ac.start() + ac.configure() return ac def get_one_online_account(self, pre_generated_key=True, mvbox=False, move=False): ac1 = self.get_online_configuring_account( pre_generated_key=pre_generated_key, mvbox=mvbox, move=move) - ac1._configtracker.wait_imap_connected() - ac1._configtracker.wait_smtp_connected() - ac1._configtracker.wait_finish() + ac1.wait_configure_finish() return ac1 def get_two_online_accounts(self, move=False, quiet=False): ac1 = self.get_online_configuring_account(move=True, quiet=quiet) ac2 = self.get_online_configuring_account(quiet=quiet) - ac1._configtracker.wait_finish() - ac2._configtracker.wait_finish() + ac1.wait_configure_finish() + ac2.wait_configure_finish() return ac1, ac2 def clone_online_account(self, account, pre_generated_key=True): @@ -339,7 +334,7 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, data): mvbox_move=account.get_config("mvbox_move"), sentbox_watch=account.get_config("sentbox_watch"), )) - ac.start() + ac.configure() return ac def run_bot_process(self, module, ffi=True): diff --git a/python/tests/conftest.py b/python/tests/conftest.py deleted file mode 100644 index 6cc65dc14..000000000 --- a/python/tests/conftest.py +++ /dev/null @@ -1,18 +0,0 @@ -from __future__ import print_function - - -def wait_configuration_progress(account, min_target, max_target=1001, check_error=True): - min_target = min(min_target, max_target) - while 1: - event = account._evtracker.get_matching("DC_EVENT_CONFIGURE_PROGRESS") - if event.data1 >= min_target and event.data1 <= max_target: - print("** CONFIG PROGRESS {}".format(min_target), account) - break - - -def wait_securejoin_inviter_progress(account, target): - while 1: - event = account._evtracker.get_matching("DC_EVENT_SECUREJOIN_INVITER_PROGRESS") - if event.data2 >= target: - print("** SECUREJOINT-INVITER PROGRESS {}".format(target), account) - break diff --git a/python/tests/test_account.py b/python/tests/test_account.py index 283a619af..0cd14a9cc 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -3,13 +3,10 @@ import pytest import os import queue import time -from deltachat import const, Account, capi +from deltachat import const, Account from deltachat.message import Message from deltachat.hookspec import account_hookimpl -from deltachat.tracker import ConfigureTracker from datetime import datetime, timedelta -from conftest import (wait_configuration_progress, - wait_securejoin_inviter_progress) @pytest.mark.parametrize("msgtext,res", [ @@ -535,8 +532,8 @@ class TestOnlineAccount: ) # rsa key gen can be slow especially on CI, adjust timeout ac1._evtracker.set_timeout(120) - wait_configuration_progress(ac1, 1000) - wait_configuration_progress(ac2, 1000) + ac1.wait_configure_finish() + ac2.wait_configure_finish() chat = self.get_chat(ac1, ac2, both_created=True) lp.sec("ac1: send unencrypted message to ac2") @@ -564,12 +561,13 @@ class TestOnlineAccount: assert msg3_in.is_encrypted() def test_configure_canceled(self, acfactory): - ac1 = acfactory.get_online_configuring_account(start=False) - with ac1.temp_plugin(ConfigureTracker()) as config_tracker: - capi.lib.dc_configure(ac1._dc_context) - config_tracker.wait_progress() - ac1.stop_ongoing() - config_tracker.wait_finish() + ac1 = acfactory.get_online_configuring_account() + ac1._configtracker.wait_progress() + ac1.stop_ongoing() + try: + ac1.wait_configure_finish() + except ac1._configtracker.ConfigureFailed: + pass def test_export_import_self_keys(self, acfactory, tmpdir): ac1, ac2 = acfactory.get_two_online_accounts() @@ -590,9 +588,9 @@ class TestOnlineAccount: # are copied to it via BCC. ac1_clone = acfactory.clone_online_account(ac1) - wait_configuration_progress(ac1, 1000) - wait_configuration_progress(ac2, 1000) - wait_configuration_progress(ac1_clone, 1000) + ac1.wait_configure_finish() + ac2.wait_configure_finish() + ac1_clone.wait_configure_finish() chat = self.get_chat(ac1, ac2) @@ -696,10 +694,10 @@ class TestOnlineAccount: ac2 = acfactory.get_online_configuring_account() lp.sec("ac2: waiting for configuration") - wait_configuration_progress(ac2, 1000) + ac2.wait_configure_finish() lp.sec("ac1: waiting for configuration") - wait_configuration_progress(ac1, 1000) + ac1.wait_configure_finish() lp.sec("ac1: send message and wait for ac2 to receive it") chat = self.get_chat(ac1, ac2) @@ -711,8 +709,8 @@ class TestOnlineAccount: def test_move_works(self, acfactory): ac1 = acfactory.get_online_configuring_account() ac2 = acfactory.get_online_configuring_account(mvbox=True, move=True) - wait_configuration_progress(ac2, 1000) - wait_configuration_progress(ac1, 1000) + ac2.wait_configure_finish() + ac1.wait_configure_finish() chat = self.get_chat(ac1, ac2) chat.send_text("message1") ev = ac2._evtracker.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED") @@ -723,8 +721,8 @@ class TestOnlineAccount: ac1 = acfactory.get_online_configuring_account(mvbox=True, move=True) ac1.set_config("bcc_self", "1") ac2 = acfactory.get_online_configuring_account() - wait_configuration_progress(ac2, 1000) - wait_configuration_progress(ac1, 1000) + ac2.wait_configure_finish() + ac1.wait_configure_finish() chat = self.get_chat(ac1, ac2) chat.send_text("message1") chat.send_text("message2") @@ -1102,8 +1100,7 @@ class TestOnlineAccount: assert m == msg_in def test_import_export_online_all(self, acfactory, tmpdir, lp): - ac1 = acfactory.get_online_configuring_account() - wait_configuration_progress(ac1, 1000) + ac1 = acfactory.get_one_online_account() lp.sec("create some chat content") contact1 = ac1.create_contact("some1@hello.com", name="some1") @@ -1151,8 +1148,8 @@ class TestOnlineAccount: # as of Jul2019 ac1 = acfactory.get_online_configuring_account() ac2 = acfactory.clone_online_account(ac1) - wait_configuration_progress(ac2, 1000) - wait_configuration_progress(ac1, 1000) + ac2.wait_configure_finish() + ac1.wait_configure_finish() lp.sec("trigger ac setup message and return setupcode") assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"] @@ -1175,8 +1172,8 @@ class TestOnlineAccount: ac1 = acfactory.get_online_configuring_account() ac2 = acfactory.clone_online_account(ac1) ac2._evtracker.set_timeout(30) - wait_configuration_progress(ac2, 1000) - wait_configuration_progress(ac1, 1000) + ac2.wait_configure_finish() + ac1.wait_configure_finish() lp.sec("trigger ac setup message but ignore") assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"] @@ -1202,7 +1199,7 @@ class TestOnlineAccount: lp.sec("ac2: start QR-code based setup contact protocol") ch = ac2.qr_setup_contact(qr) assert ch.id >= 10 - wait_securejoin_inviter_progress(ac1, 1000) + ac1._evtracker.wait_securejoin_inviter_progress(1000) def test_qr_join_chat(self, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() @@ -1216,7 +1213,7 @@ class TestOnlineAccount: # check that at least some of the handshake messages are deleted ac1._evtracker.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED") ac2._evtracker.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED") - wait_securejoin_inviter_progress(ac1, 1000) + ac1._evtracker.wait_securejoin_inviter_progress(1000) def test_qr_verified_group_and_chatting(self, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() @@ -1227,7 +1224,7 @@ class TestOnlineAccount: lp.sec("ac2: start QR-code based join-group protocol") chat2 = ac2.qr_join_chat(qr) assert chat2.id >= 10 - wait_securejoin_inviter_progress(ac1, 1000) + ac1._evtracker.wait_securejoin_inviter_progress(1000) lp.sec("ac2: read member added message") msg = ac2._evtracker.wait_next_incoming_message() @@ -1487,7 +1484,7 @@ class TestGroupStressTests: lp.sec("creating and configuring five accounts") accounts = [acfactory.get_online_configuring_account() for i in range(5)] for acc in accounts: - wait_configuration_progress(acc, 1000) + acc.wait_configure_finish() ac1 = accounts.pop() lp.sec("ac1: setting up contacts with 4 other members") @@ -1591,7 +1588,7 @@ class TestGroupStressTests: lp.sec("creating and configuring five accounts") accounts = [acfactory.get_online_configuring_account() for i in range(3)] for acc in accounts: - wait_configuration_progress(acc, 1000) + acc.wait_configure_finish() ac1 = accounts.pop() lp.sec("ac1: setting up contacts with 2 other members") @@ -1660,27 +1657,26 @@ class TestOnlineConfigureFails: ac1, configdict = acfactory.get_online_config() ac1.update_config(dict(addr=configdict["addr"], mail_pw="123")) - with ac1.configure() as tracker: - tracker.wait_progress(500) - tracker.wait_progress(0) + ac1.configure() + ac1._configtracker.wait_progress(500) + ac1._configtracker.wait_progress(0) ev = ac1._evtracker.get_matching("DC_EVENT_ERROR_NETWORK") assert "cannot login" in ev.data2.lower() def test_invalid_user(self, acfactory): ac1, configdict = acfactory.get_online_config() ac1.update_config(dict(addr="x" + configdict["addr"], mail_pw=configdict["mail_pw"])) - with ac1.configure() as tracker: - tracker.wait_progress(500) - tracker.wait_progress(0) + ac1.configure() + ac1._configtracker.wait_progress(500) + ac1._configtracker.wait_progress(0) ev = ac1._evtracker.get_matching("DC_EVENT_ERROR_NETWORK") assert "cannot login" in ev.data2.lower() def test_invalid_domain(self, acfactory): ac1, configdict = acfactory.get_online_config() ac1.update_config((dict(addr=configdict["addr"] + "x", mail_pw=configdict["mail_pw"]))) - with ac1.configure() as tracker: - tracker.wait_progress(500) - tracker.wait_progress(0) - wait_configuration_progress(ac1, 500) + ac1.configure() + ac1._configtracker.wait_progress(500) + ac1._configtracker.wait_progress(0) ev = ac1._evtracker.get_matching("DC_EVENT_ERROR_NETWORK") assert "could not connect" in ev.data2.lower() diff --git a/python/tests/test_increation.py b/python/tests/test_increation.py index 6fb96e1fe..34b9050b0 100644 --- a/python/tests/test_increation.py +++ b/python/tests/test_increation.py @@ -6,24 +6,24 @@ import shutil import pytest from filecmp import cmp -from conftest import wait_configuration_progress from deltachat import const def wait_msgs_changed(account, chat_id, msg_id=None): - ev = account._evtracker.get_matching("DC_EVENT_MSGS_CHANGED") - assert ev.data1 == chat_id - if msg_id is not None: - assert ev.data2 == msg_id - return ev.data2 + account.log("waiting for chat_id={} msg_id={}".format(chat_id, msg_id)) + while 1: + ev = account._evtracker.get_matching("DC_EVENT_MSGS_CHANGED") + if ev.data1 != chat_id: + account.log("waiting got mismatched DC_EVENT_MSGS_CHANGED") + continue + if msg_id is not None: + assert ev.data2 == msg_id + return ev.data2 class TestOnlineInCreation: def test_increation_not_blobdir(self, tmpdir, acfactory, lp): - ac1 = acfactory.get_online_configuring_account() - ac2 = acfactory.get_online_configuring_account() - wait_configuration_progress(ac1, 1000) - wait_configuration_progress(ac2, 1000) + ac1, ac2 = acfactory.get_two_online_accounts() c2 = ac1.create_contact(email=ac2.get_config("addr")) chat = ac1.create_chat_by_contact(c2) @@ -35,10 +35,7 @@ class TestOnlineInCreation: chat.prepare_message_file(src.strpath) def test_no_increation_copies_to_blobdir(self, tmpdir, acfactory, lp): - ac1 = acfactory.get_online_configuring_account() - ac2 = acfactory.get_online_configuring_account() - wait_configuration_progress(ac1, 1000) - wait_configuration_progress(ac2, 1000) + ac1, ac2 = acfactory.get_two_online_accounts() c2 = ac1.create_contact(email=ac2.get_config("addr")) chat = ac1.create_chat_by_contact(c2) @@ -53,10 +50,7 @@ class TestOnlineInCreation: assert os.path.exists(blob_src), "file.txt not copied to blobdir" def test_forward_increation(self, acfactory, data, lp): - ac1 = acfactory.get_online_configuring_account() - ac2 = acfactory.get_online_configuring_account() - wait_configuration_progress(ac1, 1000) - wait_configuration_progress(ac2, 1000) + ac1, ac2 = acfactory.get_two_online_accounts() c2 = ac1.create_contact(email=ac2.get_config("addr")) chat = ac1.create_chat_by_contact(c2) From 9817ccebcfd110328bd41903e557618e6986824e Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Thu, 21 May 2020 12:00:25 +0200 Subject: [PATCH 065/118] improve logging --- src/qr.rs | 5 ----- src/scheduler.rs | 8 ++++++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/qr.rs b/src/qr.rs index 9ae1cd95d..fa2783823 100644 --- a/src/qr.rs +++ b/src/qr.rs @@ -136,11 +136,6 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Lot { return format_err!("Bad fingerprint length in QR code").into(); } - println!( - "{:?} {:?} {:?} {:?} {:?} {:?} {:?}", - addr, name, invitenumber, auth, grpid, grpname, fingerprint - ); - let mut lot = Lot::new(); // retrieve known state for this fingerprint diff --git a/src/scheduler.rs b/src/scheduler.rs index b9e98a43d..f6444e386 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -247,13 +247,17 @@ async fn smtp_loop(ctx: Context, started: Sender<()>, smtp_handlers: SmtpConnect .await { Ok(Some(job)) => { + info!(ctx, "executing smtp job"); job::perform_job(&ctx, job::Connection::Smtp(&mut connection), job).await; ctx.scheduler.write().await.set_probe_network(false); } Ok(None) | Err(async_std::future::TimeoutError { .. }) => { + info!(ctx, "smpt fake idle"); // Fake Idle - async_std::task::sleep(Duration::from_millis(500)) - .race(idle_interrupt_receiver.recv().map(|_| ())) + async_std::task::sleep(Duration::from_secs(5)) + .race(idle_interrupt_receiver.recv().map(|_| { + info!(ctx, "smtp idle interrupt"); + })) .await; } } From e8e82d97601f2165001ad33a6301d06aba70ea52 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Thu, 21 May 2020 14:35:07 +0200 Subject: [PATCH 066/118] improve error handling and fix sleeps --- src/chat.rs | 4 +- src/job.rs | 134 +++++++++++++++++++++++++--------------------- src/scheduler.rs | 40 ++++++++------ src/securejoin.rs | 37 ++++++++----- 4 files changed, 124 insertions(+), 91 deletions(-) diff --git a/src/chat.rs b/src/chat.rs index 15f77375c..546f9737b 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -3209,11 +3209,11 @@ mod tests { .await .unwrap() .chat_id; - std::thread::sleep(std::time::Duration::from_millis(1000)); + async_std::task::sleep(std::time::Duration::from_millis(1000)).await; let chat_id2 = create_by_contact_id(&t.ctx, DC_CONTACT_ID_SELF) .await .unwrap(); - std::thread::sleep(std::time::Duration::from_millis(1000)); + async_std::task::sleep(std::time::Duration::from_millis(1000)).await; let chat_id3 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo") .await .unwrap(); diff --git a/src/job.rs b/src/job.rs index 7da9b6186..b80bf9607 100644 --- a/src/job.rs +++ b/src/job.rs @@ -172,25 +172,25 @@ impl Job { } /// Deletes the job from the database. - async fn delete(&self, context: &Context) -> bool { + async fn delete(self, context: &Context) -> Result<()> { if self.job_id != 0 { context .sql .execute("DELETE FROM jobs WHERE id=?;", paramsv![self.job_id as i32]) - .await - .is_ok() - } else { - // Already deleted. - true + .await?; } + + Ok(()) } /// Saves the job to the database, creating a new entry if necessary. /// /// The Job is consumed by this method. - async fn save(self, context: &Context) -> bool { + async fn save(self, context: &Context) -> Result<()> { let thread: Thread = self.action.into(); + info!(context, "saving job for {}-thread: {:?}", thread, self); + if self.job_id != 0 { context .sql @@ -203,8 +203,7 @@ impl Job { self.job_id as i32, ], ) - .await - .is_ok() + .await?; } else { context.sql.execute( "INSERT INTO jobs (added_timestamp, thread, action, foreign_id, param, desired_timestamp) VALUES (?,?,?,?,?,?);", @@ -216,8 +215,10 @@ impl Job { self.param.to_string(), self.desired_timestamp ] - ).await.is_ok() + ).await?; } + + Ok(()) } async fn smtp_send( @@ -896,7 +897,9 @@ pub(crate) async fn perform_job(context: &Context, mut connection: Connection<'_ tries, time_offset ); - job.save(context).await; + job.save(context).await.unwrap_or_else(|err| { + error!(context, "failed to save job: {}", err); + }); } else { info!( context, @@ -905,7 +908,9 @@ pub(crate) async fn perform_job(context: &Context, mut connection: Connection<'_ job, JOB_RETRIES ); - job.delete(context).await; + job.delete(context).await.unwrap_or_else(|err| { + error!(context, "failed to delete job: {}", err); + }); } } Status::Finished(res) => { @@ -921,7 +926,9 @@ pub(crate) async fn perform_job(context: &Context, mut connection: Connection<'_ ); } - job.delete(context).await; + job.delete(context).await.unwrap_or_else(|err| { + error!(context, "failed to delete job: {}", err); + }); } } } @@ -1019,7 +1026,9 @@ pub async fn add( } let job = Job::new(action, foreign_id as u32, param, delay_seconds); - job.save(context).await; + job.save(context).await.unwrap_or_else(|err| { + error!(context, "failed to save job: {}", err); + }); if delay_seconds == 0 { match action { @@ -1053,76 +1062,79 @@ pub(crate) async fn load_next( thread: Thread, probe_network: bool, ) -> Option { + info!(context, "loading job for {}-thread", thread); let query = if !probe_network { // processing for first-try and after backoff-timeouts: // process jobs in the order they were added. - "SELECT id, action, foreign_id, param, added_timestamp, desired_timestamp, tries \ - FROM jobs WHERE thread=? AND desired_timestamp<=? ORDER BY action DESC, added_timestamp;" + r#" +SELECT id, action, foreign_id, param, added_timestamp, desired_timestamp, tries +FROM jobs +WHERE thread=? AND desired_timestamp<=? +ORDER BY action DESC, added_timestamp +LIMIT 1; +"# } else { // processing after call to dc_maybe_network(): // process _all_ pending jobs that failed before // in the order of their backoff-times. - "SELECT id, action, foreign_id, param, added_timestamp, desired_timestamp, tries \ - FROM jobs WHERE thread=? AND tries>0 ORDER BY desired_timestamp, action DESC;" + r#" +SELECT id, action, foreign_id, param, added_timestamp, desired_timestamp, tries +FROM jobs +WHERE thread=? AND tries>0 +ORDER BY desired_timestamp, action DESC +LIMIT 1; +"# }; let thread_i = thread as i64; let t = time(); - let params_no_probe = paramsv![thread_i, t]; - let params_probe = paramsv![thread_i]; let params = if !probe_network { - params_no_probe + paramsv![thread_i, t] } else { - params_probe + paramsv![thread_i] }; let job = context .sql - .query_map( - query, - params, - |row| { - let job = Job { - job_id: row.get(0)?, - action: row.get(1)?, - foreign_id: row.get(2)?, - desired_timestamp: row.get(5)?, - added_timestamp: row.get(4)?, - tries: row.get(6)?, - param: row.get::<_, String>(3)?.parse().unwrap_or_default(), - pending_error: None, - }; + .query_row_optional(query, params, |row| { + let job = Job { + job_id: row.get(0)?, + action: row.get(1)?, + foreign_id: row.get(2)?, + desired_timestamp: row.get(5)?, + added_timestamp: row.get(4)?, + tries: row.get(6)?, + param: row.get::<_, String>(3)?.parse().unwrap_or_default(), + pending_error: None, + }; - Ok(job) - }, - |jobs| { - for job in jobs { - match job { - Ok(j) => return Ok(Some(j)), - Err(e) => warn!(context, "Bad job from the database: {}", e), + Ok(job) + }) + .await; + + match job { + Ok(job) => { + if thread == Thread::Imap { + if let Some(job) = job { + if job.action < Action::DeleteMsgOnImap { + load_imap_deletion_job(context) + .await + .unwrap_or_default() + .or(Some(job)) + } else { + Some(job) } + } else { + load_imap_deletion_job(context).await.unwrap_or_default() } - Ok(None) - }, - ) - .await - .unwrap_or_default(); - - if thread == Thread::Imap { - if let Some(job) = job { - if job.action < Action::DeleteMsgOnImap { - load_imap_deletion_job(context) - .await - .unwrap_or_default() - .or(Some(job)) } else { - Some(job) + job } - } else { - load_imap_deletion_job(context).await.unwrap_or_default() } - } else { - job + Err(err) => { + warn!(context, "Bad job from the database: {}", err); + None + } } } diff --git a/src/scheduler.rs b/src/scheduler.rs index f6444e386..7573f9c41 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -252,13 +252,13 @@ async fn smtp_loop(ctx: Context, started: Sender<()>, smtp_handlers: SmtpConnect ctx.scheduler.write().await.set_probe_network(false); } Ok(None) | Err(async_std::future::TimeoutError { .. }) => { - info!(ctx, "smpt fake idle"); + info!(ctx, "smtp fake idle"); // Fake Idle - async_std::task::sleep(Duration::from_secs(5)) - .race(idle_interrupt_receiver.recv().map(|_| { - info!(ctx, "smtp idle interrupt"); - })) - .await; + idle_interrupt_receiver + .recv() + .timeout(Duration::from_secs(5)) + .await + .ok(); } } } @@ -384,28 +384,36 @@ impl Scheduler { async fn interrupt_inbox(&self) { match self { - Scheduler::Running { ref inbox, .. } => inbox.interrupt().await, + Scheduler::Running { ref inbox, .. } => { + inbox.interrupt().await.ok(); + } _ => {} } } async fn interrupt_mvbox(&self) { match self { - Scheduler::Running { ref mvbox, .. } => mvbox.interrupt().await, + Scheduler::Running { ref mvbox, .. } => { + mvbox.interrupt().await.ok(); + } _ => {} } } async fn interrupt_sentbox(&self) { match self { - Scheduler::Running { ref sentbox, .. } => sentbox.interrupt().await, + Scheduler::Running { ref sentbox, .. } => { + sentbox.interrupt().await.ok(); + } _ => {} } } async fn interrupt_smtp(&self) { match self { - Scheduler::Running { ref smtp, .. } => smtp.interrupt().await, + Scheduler::Running { ref smtp, .. } => { + smtp.interrupt().await.ok(); + } _ => {} } } @@ -487,9 +495,9 @@ impl ConnectionState { self.shutdown_receiver.recv().await.ok(); } - async fn interrupt(&self) { + async fn interrupt(&self) -> Result<(), async_std::sync::TrySendError<()>> { // Use try_send to avoid blocking on interrupts. - self.idle_interrupt_sender.try_send(()).ok(); + self.idle_interrupt_sender.try_send(()) } } @@ -523,8 +531,8 @@ impl SmtpConnectionState { } /// Interrupt any form of idle. - async fn interrupt(&self) { - self.state.interrupt().await; + async fn interrupt(&self) -> Result<(), async_std::sync::TrySendError<()>> { + self.state.interrupt().await } /// Shutdown this connection completely. @@ -571,8 +579,8 @@ impl ImapConnectionState { } /// Interrupt any form of idle. - async fn interrupt(&self) { - self.state.interrupt().await; + async fn interrupt(&self) -> Result<(), async_std::sync::TrySendError<()>> { + self.state.interrupt().await } /// Shutdown this connection completely. diff --git a/src/securejoin.rs b/src/securejoin.rs index c0d14802c..2a46fad88 100644 --- a/src/securejoin.rs +++ b/src/securejoin.rs @@ -258,7 +258,7 @@ async fn securejoin(context: &Context, qr: &str) -> ChatId { let own_fingerprint = get_self_fingerprint(context).await.unwrap_or_default(); // Bob -> Alice - send_handshake_msg( + if let Err(err) = send_handshake_msg( context, contact_chat_id, if join_vg { @@ -274,12 +274,16 @@ async fn securejoin(context: &Context, qr: &str) -> ChatId { "".to_string() }, ) - .await; + .await + { + error!(context, "failed to send handshake message: {}", err); + return cleanup(&context, contact_chat_id, true, join_vg).await; + } } else { context.bob.write().await.expects = DC_VC_AUTH_REQUIRED; // Bob -> Alice - send_handshake_msg( + if let Err(err) = send_handshake_msg( context, contact_chat_id, if join_vg { "vg-request" } else { "vc-request" }, @@ -287,13 +291,17 @@ async fn securejoin(context: &Context, qr: &str) -> ChatId { None, "", ) - .await; + .await + { + error!(context, "failed to send handshake message: {}", err); + return cleanup(&context, contact_chat_id, true, join_vg).await; + } } if join_vg { // for a group-join, wait until the secure-join is done and the group is created while !context.shall_stop_ongoing().await { - std::thread::sleep(std::time::Duration::from_millis(200)); + async_std::task::sleep(std::time::Duration::from_millis(200)).await; } cleanup(&context, contact_chat_id, true, join_vg).await } else { @@ -311,7 +319,7 @@ async fn send_handshake_msg( param2: impl AsRef, fingerprint: Option, grpid: impl AsRef, -) { +) -> Result<(), HandshakeError> { let mut msg = Message::default(); msg.viewtype = Viewtype::Text; msg.text = Some(format!("Secure-Join: {}", step)); @@ -339,10 +347,12 @@ async fn send_handshake_msg( } else { msg.param.set_int(Param::GuaranteeE2ee, 1); } - // TODO. handle cleanup on error + chat::send_msg(context, contact_chat_id, &mut msg) .await - .unwrap_or_default(); + .map_err(|err| HandshakeError::MsgSendFailed(err))?; + + Ok(()) } async fn chat_id_2_contact_id(context: &Context, contact_chat_id: ChatId) -> u32 { @@ -391,6 +401,8 @@ pub(crate) enum HandshakeError { ChatNotFound { group: String }, #[error("No configured self address found")] NoSelfAddr, + #[error("Failed to send message")] + MsgSendFailed(#[source] Error), } /// What to do with a Secure-Join handshake message after it was handled. @@ -487,7 +499,7 @@ pub(crate) async fn handle_securejoin_handshake( None, "", ) - .await; + .await?; Ok(HandshakeMessage::Done) } "vg-auth-required" | "vc-auth-required" => { @@ -559,7 +571,7 @@ pub(crate) async fn handle_securejoin_handshake( "".to_string() }, ) - .await; + .await?; Ok(HandshakeMessage::Done) } "vg-request-with-auth" | "vc-request-with-auth" => { @@ -670,7 +682,8 @@ pub(crate) async fn handle_securejoin_handshake( Some(fingerprint.clone()), "", ) - .await; + .await?; + inviter_progress!(context, contact_id, 1000); } Ok(HandshakeMessage::Ignore) // "Done" would delete the message and break multi-device (the key from Autocrypt-header is needed) @@ -779,7 +792,7 @@ pub(crate) async fn handle_securejoin_handshake( Some(scanned_fingerprint_of_alice), "", ) - .await; + .await?; context.bob.write().await.status = 1; context.stop_ongoing().await; From c89d7b5b181c8bb57cadf730a0b1bc4446cbf19f Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Thu, 21 May 2020 14:48:55 +0200 Subject: [PATCH 067/118] fix and improve load_next job logic --- src/job.rs | 87 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 54 insertions(+), 33 deletions(-) diff --git a/src/job.rs b/src/job.rs index b80bf9607..d548c5ce2 100644 --- a/src/job.rs +++ b/src/job.rs @@ -1094,47 +1094,68 @@ LIMIT 1; paramsv![thread_i] }; - let job = context - .sql - .query_row_optional(query, params, |row| { - let job = Job { - job_id: row.get(0)?, - action: row.get(1)?, - foreign_id: row.get(2)?, - desired_timestamp: row.get(5)?, - added_timestamp: row.get(4)?, - tries: row.get(6)?, - param: row.get::<_, String>(3)?.parse().unwrap_or_default(), - pending_error: None, - }; + let job = loop { + let job_res = context + .sql + .query_row_optional(query, params.clone(), |row| { + let job = Job { + job_id: row.get(0)?, + action: row.get(1)?, + foreign_id: row.get(2)?, + desired_timestamp: row.get(5)?, + added_timestamp: row.get(4)?, + tries: row.get(6)?, + param: row.get::<_, String>(3)?.parse().unwrap_or_default(), + pending_error: None, + }; - Ok(job) - }) - .await; + Ok(job) + }) + .await; - match job { - Ok(job) => { - if thread == Thread::Imap { - if let Some(job) = job { - if job.action < Action::DeleteMsgOnImap { - load_imap_deletion_job(context) + dbg!(&job_res); + match job_res { + Ok(job) => break job, + Err(_) => { + // Remove invalid job from the DB + + // TODO: improve by only doing a single query + match context + .sql + .query_row(query, params.clone(), |row| row.get::<_, i32>(0)) + .await + { + Ok(id) => { + context + .sql + .execute("DELETE FROM jobs WHERE id=?", paramsv![id]) .await - .unwrap_or_default() - .or(Some(job)) - } else { - Some(job) + .ok(); + } + Err(err) => { + error!(context, "failed to retrieve invalid job from DB: {}", err); + break None; } - } else { - load_imap_deletion_job(context).await.unwrap_or_default() } - } else { - job } } - Err(err) => { - warn!(context, "Bad job from the database: {}", err); - None + }; + + if thread == Thread::Imap { + if let Some(job) = job { + if job.action < Action::DeleteMsgOnImap { + load_imap_deletion_job(context) + .await + .unwrap_or_default() + .or(Some(job)) + } else { + Some(job) + } + } else { + load_imap_deletion_job(context).await.unwrap_or_default() } + } else { + job } } From 82c85566dccf648b206a19c78b8f7c04b8dcfd53 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Thu, 21 May 2020 15:09:32 +0200 Subject: [PATCH 068/118] fix securejoin cancelation --- src/job.rs | 1 - src/securejoin.rs | 20 +++++++------------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/job.rs b/src/job.rs index d548c5ce2..fd4839b4d 100644 --- a/src/job.rs +++ b/src/job.rs @@ -1113,7 +1113,6 @@ LIMIT 1; }) .await; - dbg!(&job_res); match job_res { Ok(job) => break job, Err(_) => { diff --git a/src/securejoin.rs b/src/securejoin.rs index 2a46fad88..aab4dab5c 100644 --- a/src/securejoin.rs +++ b/src/securejoin.rs @@ -1,6 +1,7 @@ //! Verified contact protocol implementation as [specified by countermitm project](https://countermitm.readthedocs.io/en/stable/new.html#setup-contact-protocol) -use async_std::prelude::*; +use std::time::Duration; + use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC}; use crate::aheader::EncryptPreference; @@ -183,18 +184,11 @@ async fn cleanup( /// Take a scanned QR-code and do the setup-contact/join-group handshake. /// See the ffi-documentation for more details. pub async fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId { - use futures::future::FutureExt; + if let Err(_) = context.alloc_ongoing().await { + return cleanup(&context, ChatId::new(0), false, false).await; + } - let cancel = match context.alloc_ongoing().await { - Ok(cancel) => cancel, - Err(_) => { - return cleanup(&context, ChatId::new(0), false, false).await; - } - }; - - securejoin(context, qr) - .race(cancel.recv().map(|_| ChatId::new(0))) - .await + securejoin(context, qr).await } async fn securejoin(context: &Context, qr: &str) -> ChatId { @@ -301,7 +295,7 @@ async fn securejoin(context: &Context, qr: &str) -> ChatId { if join_vg { // for a group-join, wait until the secure-join is done and the group is created while !context.shall_stop_ongoing().await { - async_std::task::sleep(std::time::Duration::from_millis(200)).await; + async_std::task::sleep(Duration::from_millis(50)).await; } cleanup(&context, contact_chat_id, true, join_vg).await } else { From 7b10ec26a3270fb9f4f736f6f9ce348377888bf7 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Thu, 21 May 2020 15:38:04 +0200 Subject: [PATCH 069/118] improve connection management --- python/src/deltachat/testplugin.py | 3 + python/tests/test_account.py | 123 ++++++++++++++++++++++++++++- src/configure/mod.rs | 8 +- 3 files changed, 126 insertions(+), 8 deletions(-) diff --git a/python/src/deltachat/testplugin.py b/python/src/deltachat/testplugin.py index 910db5c71..ddc8ee9fc 100644 --- a/python/src/deltachat/testplugin.py +++ b/python/src/deltachat/testplugin.py @@ -310,13 +310,16 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, data): ac1 = self.get_online_configuring_account( pre_generated_key=pre_generated_key, mvbox=mvbox, move=move) ac1.wait_configure_finish() + ac1.start() return ac1 def get_two_online_accounts(self, move=False, quiet=False): ac1 = self.get_online_configuring_account(move=True, quiet=quiet) ac2 = self.get_online_configuring_account(quiet=quiet) ac1.wait_configure_finish() + ac1.start() ac2.wait_configure_finish() + ac2.start() return ac1, ac2 def clone_online_account(self, account, pre_generated_key=True): diff --git a/python/tests/test_account.py b/python/tests/test_account.py index 0cd14a9cc..e68c204a4 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -533,7 +533,9 @@ class TestOnlineAccount: # rsa key gen can be slow especially on CI, adjust timeout ac1._evtracker.set_timeout(120) ac1.wait_configure_finish() + ac1.start() ac2.wait_configure_finish() + ac2.start() chat = self.get_chat(ac1, ac2, both_created=True) lp.sec("ac1: send unencrypted message to ac2") @@ -560,7 +562,10 @@ class TestOnlineAccount: assert msg3_in.text == "message3" assert msg3_in.is_encrypted() - def test_configure_canceled(self, acfactory): + ac1.shutdown() + ac2.shutdown() + + def xtest_configure_canceled(self, acfactory): ac1 = acfactory.get_online_configuring_account() ac1._configtracker.wait_progress() ac1.stop_ongoing() @@ -569,6 +574,8 @@ class TestOnlineAccount: except ac1._configtracker.ConfigureFailed: pass + ac1.shutdown() + def test_export_import_self_keys(self, acfactory, tmpdir): ac1, ac2 = acfactory.get_two_online_accounts() @@ -580,6 +587,9 @@ class TestOnlineAccount: ac1._evtracker.consume_events() ac2.import_self_keys(dir.strpath) + ac1.shutdown() + ac2.shutdown() + def test_one_account_send_bcc_setting(self, acfactory, lp): ac1 = acfactory.get_online_configuring_account() ac2 = acfactory.get_online_configuring_account() @@ -589,8 +599,11 @@ class TestOnlineAccount: ac1_clone = acfactory.clone_online_account(ac1) ac1.wait_configure_finish() + ac1.start() ac2.wait_configure_finish() + ac2.start() ac1_clone.wait_configure_finish() + ac1_clone.start() chat = self.get_chat(ac1, ac2) @@ -630,6 +643,10 @@ class TestOnlineAccount: ev_msg = ac1_clone._evtracker.wait_next_messages_changed() assert ev_msg.text == msg_out.text + ac1.shutdown() + ac2.shutdown() + ac1_clone.shutdown() + def test_send_file_twice_unicode_filename_mangling(self, tmpdir, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() chat = self.get_chat(ac1, ac2) @@ -662,6 +679,9 @@ class TestOnlineAccount: assert msg2.filename.endswith("html.zip") assert msg.filename != msg2.filename + ac1.shutdown() + ac2.shutdown() + def test_send_file_html_attachment(self, tmpdir, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() chat = self.get_chat(ac1, ac2) @@ -686,6 +706,9 @@ class TestOnlineAccount: assert open(msg.filename).read() == content assert msg.filename.endswith(basename) + ac1.shutdown() + ac2.shutdown() + def test_mvbox_sentbox_threads(self, acfactory, lp): lp.sec("ac1: start with mvbox thread") ac1 = acfactory.get_online_configuring_account(mvbox=True, move=True, sentbox=True) @@ -695,9 +718,11 @@ class TestOnlineAccount: lp.sec("ac2: waiting for configuration") ac2.wait_configure_finish() + ac2.start() lp.sec("ac1: waiting for configuration") ac1.wait_configure_finish() + ac1.start() lp.sec("ac1: send message and wait for ac2 to receive it") chat = self.get_chat(ac1, ac2) @@ -706,23 +731,34 @@ class TestOnlineAccount: assert ev.data2 > const.DC_CHAT_ID_LAST_SPECIAL lp.sec("test finished") + ac1.shutdown() + ac2.shutdown() + def test_move_works(self, acfactory): ac1 = acfactory.get_online_configuring_account() ac2 = acfactory.get_online_configuring_account(mvbox=True, move=True) ac2.wait_configure_finish() + ac2.start() ac1.wait_configure_finish() + ac1.start() chat = self.get_chat(ac1, ac2) chat.send_text("message1") ev = ac2._evtracker.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED") assert ev.data2 > const.DC_CHAT_ID_LAST_SPECIAL ac2._evtracker.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED") + ac1.shutdown() + ac2.shutdown() + def test_move_works_on_self_sent(self, acfactory): ac1 = acfactory.get_online_configuring_account(mvbox=True, move=True) ac1.set_config("bcc_self", "1") ac2 = acfactory.get_online_configuring_account() ac2.wait_configure_finish() + ac2.start() ac1.wait_configure_finish() + ac1.start() + chat = self.get_chat(ac1, ac2) chat.send_text("message1") chat.send_text("message2") @@ -731,6 +767,9 @@ class TestOnlineAccount: ac1._evtracker.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED") ac1._evtracker.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED") + ac1.shutdown() + ac2.shutdown() + def test_forward_messages(self, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() chat = self.get_chat(ac1, ac2) @@ -764,6 +803,9 @@ class TestOnlineAccount: ac2.delete_messages(messages) assert not chat3.get_messages() + ac1.shutdown() + ac2.shutdown() + def test_forward_own_message(self, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() chat = self.get_chat(ac1, ac2, both_created=True) @@ -788,6 +830,9 @@ class TestOnlineAccount: assert msg_in.text == "message2" assert msg_in.is_forwarded() + ac1.shutdown() + ac2.shutdown() + def test_send_self_message_and_empty_folder(self, acfactory, lp): ac1 = acfactory.get_one_online_account(mvbox=True, move=True) lp.sec("ac1: create self chat") @@ -800,6 +845,8 @@ class TestOnlineAccount: boxes = sorted([ev1.data2, ev2.data2]) assert boxes == ["DeltaChat", "INBOX"] + ac1.shutdown() + def test_send_and_receive_message_markseen(self, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() @@ -869,6 +916,10 @@ class TestOnlineAccount: except queue.Empty: pass # mark_seen_messages() has generated events before it returns + ac1.shutdown() + ac2.shutdown() + + def test_mdn_asymetric(self, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts(move=True) @@ -904,6 +955,9 @@ class TestOnlineAccount: # MDN is received even though MDNs are already disabled assert msg_out.is_out_mdn_received() + ac1.shutdown() + ac2.shutdown() + def test_send_and_receive_will_encrypt_decrypt(self, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() @@ -953,6 +1007,9 @@ class TestOnlineAccount: ev = ac1._evtracker.get_matching("DC_EVENT_SMTP_MESSAGE_SENT") assert not msg.is_encrypted() + ac1.shutdown() + ac2.shutdown() + def test_send_first_message_as_long_unicode_with_cr(self, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() ac2.set_config("save_mime_headers", "1") @@ -979,6 +1036,9 @@ class TestOnlineAccount: assert msg_in.text == text2 assert ac1.get_config("addr") in msg_in.chat.get_name() + ac1.shutdown() + ac2.shutdown() + def test_reply_encrypted(self, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() @@ -1032,6 +1092,9 @@ class TestOnlineAccount: assert msg_in.text == "message2 -- should be encrypted" assert msg_in.is_encrypted() + ac1.shutdown() + ac2.shutdown() + def test_saved_mime_on_received_message(self, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() @@ -1051,6 +1114,9 @@ class TestOnlineAccount: assert mime.get_all("From") assert mime.get_all("Received") + ac1.shutdown() + ac2.shutdown() + def test_send_and_receive_image(self, acfactory, lp, data): ac1, ac2 = acfactory.get_two_online_accounts() chat = self.get_chat(ac1, ac2) @@ -1099,6 +1165,9 @@ class TestOnlineAccount: m = message_queue.get() assert m == msg_in + ac1.shutdown() + ac2.shutdown() + def test_import_export_online_all(self, acfactory, tmpdir, lp): ac1 = acfactory.get_one_online_account() @@ -1142,6 +1211,10 @@ class TestOnlineAccount: assert path2 != path assert ac2.get_latest_backupfile(backupdir.strpath) == path2 + ac1.shutdown() + ac2.shutdown() + + def test_ac_setup_message(self, acfactory, lp): # note that the receiving account needs to be configured and running # before ther setup message is send. DC does not read old messages @@ -1149,7 +1222,9 @@ class TestOnlineAccount: ac1 = acfactory.get_online_configuring_account() ac2 = acfactory.clone_online_account(ac1) ac2.wait_configure_finish() + ac2.start() ac1.wait_configure_finish() + ac1.start() lp.sec("trigger ac setup message and return setupcode") assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"] @@ -1168,12 +1243,18 @@ class TestOnlineAccount: msg.continue_key_transfer(setup_code) assert ac1.get_info()["fingerprint"] == ac2.get_info()["fingerprint"] + ac1.shutdown() + ac2.shutdown() + + def test_ac_setup_message_twice(self, acfactory, lp): ac1 = acfactory.get_online_configuring_account() ac2 = acfactory.clone_online_account(ac1) ac2._evtracker.set_timeout(30) ac2.wait_configure_finish() + ac2.start() ac1.wait_configure_finish() + ac1.start() lp.sec("trigger ac setup message but ignore") assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"] @@ -1191,6 +1272,9 @@ class TestOnlineAccount: msg.continue_key_transfer(setup_code2) assert ac1.get_info()["fingerprint"] == ac2.get_info()["fingerprint"] + ac1.shutdown() + ac2.shutdown() + def test_qr_setup_contact(self, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() lp.sec("ac1: create QR code and let ac2 scan it, starting the securejoin") @@ -1201,6 +1285,9 @@ class TestOnlineAccount: assert ch.id >= 10 ac1._evtracker.wait_securejoin_inviter_progress(1000) + ac1.shutdown() + ac2.shutdown() + def test_qr_join_chat(self, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() lp.sec("ac1: create QR code and let ac2 scan it, starting the securejoin") @@ -1215,6 +1302,9 @@ class TestOnlineAccount: ac2._evtracker.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED") ac1._evtracker.wait_securejoin_inviter_progress(1000) + ac1.shutdown() + ac2.shutdown() + def test_qr_verified_group_and_chatting(self, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() lp.sec("ac1: create verified-group QR, ac2 scans and joins") @@ -1247,6 +1337,9 @@ class TestOnlineAccount: assert msg.text == "world" assert msg.is_encrypted() + ac1.shutdown() + ac2.shutdown() + def test_set_get_contact_avatar(self, acfactory, data, lp): lp.sec("configuring ac1 and ac2") ac1, ac2 = acfactory.get_two_online_accounts() @@ -1294,6 +1387,9 @@ class TestOnlineAccount: msg3 = ac2._evtracker.wait_next_incoming_message() assert msg3.get_sender_contact().get_profile_image() is None + ac1.shutdown() + ac2.shutdown() + def test_add_remove_member_remote_events(self, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() ac1_addr = ac1.get_config("addr") @@ -1368,6 +1464,9 @@ class TestOnlineAccount: assert ev.action == "removed" assert ev.message.get_sender_contact().addr == ac1_addr + ac1.shutdown() + ac2.shutdown() + def test_set_get_group_image(self, acfactory, data, lp): ac1, ac2 = acfactory.get_two_online_accounts() @@ -1421,6 +1520,9 @@ class TestOnlineAccount: assert chat1b.get_profile_image() is None assert chat.get_profile_image() is None + ac1.shutdown() + ac2.shutdown() + def test_accept_sender_contact(self, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() ch = ac1.create_chat_by_contact(ac1.create_contact(ac2.get_config("addr"))) @@ -1430,6 +1532,9 @@ class TestOnlineAccount: msg.accept_sender_contact() assert not msg.chat.is_deaddrop() + ac1.shutdown() + ac2.shutdown() + def test_send_receive_locations(self, acfactory, lp): now = datetime.utcnow() ac1, ac2 = acfactory.get_two_online_accounts() @@ -1478,6 +1583,9 @@ class TestOnlineAccount: locations3 = chat2.get_locations(contact=contact) assert not locations3 + ac1.shutdown() + ac2.shutdown() + class TestGroupStressTests: def test_group_many_members_add_leave_remove(self, acfactory, lp): @@ -1485,6 +1593,7 @@ class TestGroupStressTests: accounts = [acfactory.get_online_configuring_account() for i in range(5)] for acc in accounts: acc.wait_configure_finish() + acc.start() ac1 = accounts.pop() lp.sec("ac1: setting up contacts with 4 other members") @@ -1577,6 +1686,12 @@ class TestGroupStressTests: # Message should be encrypted because keys of other members are gossiped assert msg.is_encrypted() + ac1.shutdown() + ac2.shutdown() + ac3.shutdown() + ac4.shutdown() + ac5.shutdown() + def test_synchronize_member_list_on_group_rejoin(self, acfactory, lp): """ Test that user recreates group member list when it joins the group again. @@ -1589,6 +1704,7 @@ class TestGroupStressTests: accounts = [acfactory.get_online_configuring_account() for i in range(3)] for acc in accounts: acc.wait_configure_finish() + acc.start() ac1 = accounts.pop() lp.sec("ac1: setting up contacts with 2 other members") @@ -1650,7 +1766,10 @@ class TestGroupStressTests: msg = ac2._evtracker.wait_next_incoming_message() assert len(msg.chat.get_contacts()) == len(chat.get_contacts()) - + + ac1.shutdown() + ac2.shutdown() + ac3.shutdown() class TestOnlineConfigureFails: def test_invalid_password(self, acfactory): diff --git a/src/configure/mod.rs b/src/configure/mod.rs index 96b21fb9d..619e359a0 100644 --- a/src/configure/mod.rs +++ b/src/configure/mod.rs @@ -83,7 +83,6 @@ impl Context { let mut step_counter: u8 = 0; let (_s, r) = async_std::sync::channel(1); let mut imap = Imap::new(r); - let mut is_imap_connected = false; let was_configured_before = self.is_configured().await; while !self.shall_stop_ongoing().await { @@ -92,7 +91,6 @@ impl Context { match exec_step( self, &mut imap, - &mut is_imap_connected, &mut param, &mut param_domain, &mut param_autoconfig, @@ -117,7 +115,7 @@ impl Context { } } - if is_imap_connected { + if imap.is_connected() { imap.disconnect(self).await; } @@ -171,7 +169,6 @@ impl Context { async fn exec_step( ctx: &Context, imap: &mut Imap, - is_imap_connected: &mut bool, param: &mut LoginParam, param_domain: &mut String, param_autoconfig: &mut Option, @@ -413,8 +410,7 @@ async fn exec_step( progress!(ctx, 600); /* try to connect to IMAP - if we did not got an autoconfig, do some further tries with different settings and username variations */ - *is_imap_connected = - try_imap_connections(ctx, param, param_autoconfig.is_some(), imap).await?; + try_imap_connections(ctx, param, param_autoconfig.is_some(), imap).await?; } 15 => { progress!(ctx, 800); From 69f095687d58ee46be74ff8d2e81c1c3594e54ff Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Thu, 21 May 2020 15:50:56 +0200 Subject: [PATCH 070/118] check correct running flag in scheduler stop --- python/src/deltachat/account.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index 0f18fcbe3..8b8f01741 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -607,7 +607,7 @@ class Account(object): self.log("stop_ongoing") self.stop_ongoing() - if self.is_started(): + if bool(lib.dc_is_running(self._dc_context)): self.log("context_shutdown (stop core scheduler)") lib.dc_context_shutdown(self._dc_context) else: From c43e7cdbdc3b2e1d14dfccf49cfa132fcec4552e Mon Sep 17 00:00:00 2001 From: holger krekel Date: Fri, 22 May 2020 10:31:41 +0200 Subject: [PATCH 071/118] fix lint and a typo --- python/tests/test_account.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/python/tests/test_account.py b/python/tests/test_account.py index e68c204a4..9ca525f59 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -758,7 +758,7 @@ class TestOnlineAccount: ac2.start() ac1.wait_configure_finish() ac1.start() - + chat = self.get_chat(ac1, ac2) chat.send_text("message1") chat.send_text("message2") @@ -919,7 +919,6 @@ class TestOnlineAccount: ac1.shutdown() ac2.shutdown() - def test_mdn_asymetric(self, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts(move=True) @@ -1213,7 +1212,6 @@ class TestOnlineAccount: ac1.shutdown() ac2.shutdown() - def test_ac_setup_message(self, acfactory, lp): # note that the receiving account needs to be configured and running @@ -1246,7 +1244,6 @@ class TestOnlineAccount: ac1.shutdown() ac2.shutdown() - def test_ac_setup_message_twice(self, acfactory, lp): ac1 = acfactory.get_online_configuring_account() ac2 = acfactory.clone_online_account(ac1) @@ -1686,11 +1683,8 @@ class TestGroupStressTests: # Message should be encrypted because keys of other members are gossiped assert msg.is_encrypted() - ac1.shutdown() - ac2.shutdown() - ac3.shutdown() - ac4.shutdown() - ac5.shutdown() + for account in accounts: + account.shutdown() def test_synchronize_member_list_on_group_rejoin(self, acfactory, lp): """ @@ -1766,15 +1760,15 @@ class TestGroupStressTests: msg = ac2._evtracker.wait_next_incoming_message() assert len(msg.chat.get_contacts()) == len(chat.get_contacts()) - + ac1.shutdown() ac2.shutdown() ac3.shutdown() + class TestOnlineConfigureFails: def test_invalid_password(self, acfactory): ac1, configdict = acfactory.get_online_config() - ac1.update_config(dict(addr=configdict["addr"], mail_pw="123")) ac1.configure() ac1._configtracker.wait_progress(500) From 70a2dbb4bbf740575f0fc70ea5402a1a11c1ed35 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Fri, 22 May 2020 11:10:26 +0200 Subject: [PATCH 072/118] back to stable async-std + use surf instead of reqwest removes tokio from our dependency tree, now only one async executor --- Cargo.lock | 525 +++++++++++++--------------------- Cargo.toml | 17 +- deltachat-ffi/Cargo.toml | 2 +- src/configure/auto_mozilla.rs | 4 +- src/configure/auto_outlook.rs | 4 +- src/configure/mod.rs | 14 +- src/configure/read_url.rs | 10 +- src/oauth2.rs | 61 +--- src/qr.rs | 22 +- src/scheduler.rs | 44 ++- 10 files changed, 258 insertions(+), 445 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5415be215..ac6866432 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -114,20 +114,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efd3d156917d94862e779f356c5acae312b08fd3121e792c857d7928c8088423" dependencies = [ "quote 1.0.6", - "syn 1.0.22", + "syn 1.0.23", +] + +[[package]] +name = "async-h1" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "effd501febce09534b924aa471e6a7fd689071fee63659473413f62a1979ae56" +dependencies = [ + "async-std", + "byte-pool", + "futures-core", + "http-types", + "httparse", + "lazy_static", + "log", + "pin-project-lite", + "url", ] [[package]] name = "async-imap" version = "0.2.0" -source = "git+https://github.com/async-email/async-imap#5e338db3b664ebcfb437dddda035fc18a78cc3ea" +source = "git+https://github.com/async-email/async-imap?rev=1e8fdd86dbc99b6d92ab4667b426092060fe8b16#1e8fdd86dbc99b6d92ab4667b426092060fe8b16" dependencies = [ "async-native-tls", "async-std", "base64 0.11.0", "byte-pool", "chrono", - "futures", + "futures 0.3.5", "imap-proto", "lazy_static", "log", @@ -141,9 +158,10 @@ dependencies = [ [[package]] name = "async-native-tls" version = "0.3.3" -source = "git+https://github.com/async-email/async-native-tls#bda408c86e84e4fa9382f50820dadcae74924320" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e9e7a929bd34c68a82d58a4de7f86fffdaf97fb2af850162a7bb19dd7269b33" dependencies = [ - "futures-util", + "async-std", "native-tls", "thiserror", "url", @@ -152,7 +170,8 @@ dependencies = [ [[package]] name = "async-smtp" version = "0.3.0" -source = "git+https://github.com/async-email/async-smtp#b2ffb61eb2ec4202c67013bfb3c7e22727c15228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb010dac8f81ceb798b089c522766c0427b54253789194b5c7de9720aeb7f091" dependencies = [ "async-native-tls", "async-std", @@ -173,34 +192,40 @@ dependencies = [ [[package]] name = "async-std" -version = "1.6.0-beta.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3bca717ee7c0abd18e11ba8bc12170e7eee097f96597a021129686bfcfd4978" +checksum = "538ecb01eb64eecd772087e5b6f7540cbc917f047727339a472dafed2185b267" dependencies = [ "async-attributes", "async-task", + "broadcaster", + "crossbeam-channel", + "crossbeam-deque", "crossbeam-utils", - "futures-channel", "futures-core", "futures-io", "futures-timer", "kv-log-macro", "log", "memchr", + "mio", + "mio-uds", "num_cpus", "once_cell", "pin-project-lite", "pin-utils", "slab", - "smol", - "wasm-bindgen-futures", ] [[package]] name = "async-task" -version = "3.0.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17772156ef2829aadc587461c7753af20b7e8db1529bc66855add962a3b35d3" +checksum = "0ac2c016b079e771204030951c366db398864f5026f84a44dafb0ff20f02085d" +dependencies = [ + "libc", + "winapi 0.3.8", +] [[package]] name = "async-trait" @@ -210,7 +235,7 @@ checksum = "26c4f3195085c36ea8d24d32b2f828d23296a9370a28aa39d111f6f16bef9f3b" dependencies = [ "proc-macro2", "quote 1.0.6", - "syn 1.0.22", + "syn 1.0.23", ] [[package]] @@ -353,6 +378,20 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "broadcaster" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c972e21e0d055a36cf73e4daae870941fe7a8abcd5ac3396aab9e4c126bd87" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "futures-util", + "parking_lot", + "slab", +] + [[package]] name = "buf_redux" version = "0.8.4" @@ -403,6 +442,16 @@ version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" +[[package]] +name = "bytes" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" +dependencies = [ + "byteorder", + "iovec", +] + [[package]] name = "bytes" version = "0.5.4" @@ -512,6 +561,15 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "cookie" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "888604f00b3db336d2af898ec3c1d5d0ddf5e6d462220f2ededc33a87ac4bbd5" +dependencies = [ + "time", +] + [[package]] name = "core-foundation" version = "0.7.0" @@ -543,20 +601,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crossbeam" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69323bff1fb41c635347b8ead484a5ca6c3f11914d784170b158d8449ab07f8e" -dependencies = [ - "cfg-if", - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-epoch", - "crossbeam-queue", - "crossbeam-utils", -] - [[package]] name = "crossbeam-channel" version = "0.4.2" @@ -621,7 +665,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf6b25ee9ac1995c54d7adb2eff8cfffb7260bc774fb63c601ec65467f43cd9d" dependencies = [ "quote 1.0.6", - "syn 1.0.22", + "syn 1.0.23", ] [[package]] @@ -658,7 +702,7 @@ dependencies = [ "proc-macro2", "quote 1.0.6", "strsim", - "syn 1.0.22", + "syn 1.0.23", ] [[package]] @@ -669,7 +713,7 @@ checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" dependencies = [ "darling_core", "quote 1.0.6", - "syn 1.0.22", + "syn 1.0.23", ] [[package]] @@ -715,7 +759,7 @@ dependencies = [ "email", "encoded-words", "escaper", - "futures", + "futures 0.3.5", "hex", "image", "image-meta", @@ -739,7 +783,6 @@ dependencies = [ "r2d2_sqlite", "rand 0.7.3", "regex", - "reqwest", "rusqlite", "rustyline", "sanitize-filename", @@ -750,9 +793,11 @@ dependencies = [ "stop-token", "strum", "strum_macros", + "surf", "tempfile", "thiserror", "thread-local-object", + "url", ] [[package]] @@ -760,7 +805,7 @@ name = "deltachat_derive" version = "2.0.0" dependencies = [ "quote 1.0.6", - "syn 1.0.22", + "syn 1.0.23", ] [[package]] @@ -787,7 +832,7 @@ dependencies = [ "derive_builder_core", "proc-macro2", "quote 1.0.6", - "syn 1.0.22", + "syn 1.0.23", ] [[package]] @@ -799,7 +844,7 @@ dependencies = [ "darling", "proc-macro2", "quote 1.0.6", - "syn 1.0.22", + "syn 1.0.23", ] [[package]] @@ -1021,7 +1066,7 @@ checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" dependencies = [ "proc-macro2", "quote 1.0.6", - "syn 1.0.22", + "syn 1.0.23", "synstructure", ] @@ -1107,6 +1152,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +[[package]] +name = "futures" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b980f2816d6ee8673b6517b52cb0e808a180efc92e5c19d02cdda79066703ef" + [[package]] name = "futures" version = "0.3.5" @@ -1164,7 +1215,7 @@ dependencies = [ "proc-macro-hack", "proc-macro2", "quote 1.0.6", - "syn 1.0.22", + "syn 1.0.23", ] [[package]] @@ -1184,13 +1235,9 @@ dependencies = [ [[package]] name = "futures-timer" -version = "3.0.2" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" -dependencies = [ - "gloo-timers", - "send_wrapper", -] +checksum = "a1de7508b218029b0f01662ed8f61b1c964b3ae99d6f25462d0f55a595109df6" [[package]] name = "futures-util" @@ -1198,6 +1245,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6" dependencies = [ + "futures 0.1.29", "futures-channel", "futures-core", "futures-io", @@ -1210,6 +1258,7 @@ dependencies = [ "proc-macro-hack", "proc-macro-nested", "slab", + "tokio-io", ] [[package]] @@ -1254,38 +1303,6 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" -[[package]] -name = "gloo-timers" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "h2" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79b7246d7e4b979c03fa093da39cfb3617a96bbeee6310af63991668d7e843ff" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "log", - "slab", - "tokio", - "tokio-util", -] - [[package]] name = "heck" version = "0.3.1" @@ -1326,19 +1343,43 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9" dependencies = [ - "bytes", + "bytes 0.5.4", "fnv", "itoa", ] [[package]] -name = "http-body" -version = "0.3.1" +name = "http-client" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" +checksum = "271fae45413daaca82e8c6bf46e452b897873676ce56196d8939645711f542d5" dependencies = [ - "bytes", + "async-h1", + "async-native-tls", + "async-std", + "futures 0.3.5", + "http-types", + "js-sys", + "log", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "http-types" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05af75a78dfeb163d472b1d27bebb6a8845917a069accdf53a9bed47aaff9bfc" +dependencies = [ + "anyhow", + "async-std", + "cookie", "http", + "infer", + "omnom", + "pin-project-lite", + "url", ] [[package]] @@ -1371,43 +1412,6 @@ dependencies = [ "quick-error", ] -[[package]] -name = "hyper" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96816e1d921eca64d208a85aab4f7798455a8e34229ee5a88c935bdee1b78b14" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "itoa", - "log", - "net2", - "pin-project", - "time", - "tokio", - "tower-service", - "want", -] - -[[package]] -name = "hyper-tls" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3adcd308402b9553630734e9c36b77a7e48b3821251ca2493e8cd596763aafaa" -dependencies = [ - "bytes", - "hyper", - "native-tls", - "tokio", - "tokio-tls", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -1469,6 +1473,15 @@ dependencies = [ "autocfg 1.0.0", ] +[[package]] +name = "infer" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d55c406a76164eb346a829ed4b97b73cb06259078eca01adeb12e8ca308d4123" +dependencies = [ + "byteorder", +] + [[package]] name = "inflate" version = "0.4.5" @@ -1746,6 +1759,17 @@ dependencies = [ "winapi 0.2.8", ] +[[package]] +name = "mio-uds" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" +dependencies = [ + "iovec", + "libc", + "mio", +] + [[package]] name = "miow" version = "0.2.1" @@ -1800,19 +1824,6 @@ dependencies = [ "void", ] -[[package]] -name = "nix" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" -dependencies = [ - "bitflags", - "cc", - "cfg-if", - "libc", - "void", -] - [[package]] name = "nodrop" version = "0.1.14" @@ -1867,7 +1878,7 @@ checksum = "0c8b15b261814f992e33760b1fca9fe8b693d8a65299f20c9901688636cfb746" dependencies = [ "proc-macro2", "quote 1.0.6", - "syn 1.0.22", + "syn 1.0.23", ] [[package]] @@ -1927,6 +1938,15 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cbca9424c482ee628fa549d9c812e2cd22f1180b9222c9200fdfa6eb31aecb2" +[[package]] +name = "omnom" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6b216cee2e0d6e680f73158d15468c80b39e571c11669cd90556f9a644e9fd3" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.4.0" @@ -2104,7 +2124,7 @@ checksum = "e58db2081ba5b4c93bd6be09c40fd36cb9193a8336c384f3b40012e531aa7e40" dependencies = [ "proc-macro2", "quote 1.0.6", - "syn 1.0.22", + "syn 1.0.23", ] [[package]] @@ -2119,16 +2139,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "piper" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6d62a6ea407d82215154475927b288219b79c8670e3371166210328e758ebaa" -dependencies = [ - "crossbeam-utils", - "futures", -] - [[package]] name = "pkg-config" version = "0.3.17" @@ -2190,9 +2200,9 @@ checksum = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694" [[package]] name = "proc-macro2" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53f5ffe53a6b28e37c9c1ce74893477864d64f74778a93a4beb43c8fa167f639" +checksum = "de40dd4ff82d9c9bab6dae29dbab1167e515f8df9ed17d2987cb6012db206933" dependencies = [ "unicode-xid 0.2.0", ] @@ -2515,43 +2525,7 @@ checksum = "475e68978dc5b743f2f40d8e0a8fdc83f1c5e78cbf4b8fa5e74e73beebc340de" dependencies = [ "proc-macro2", "quote 1.0.6", - "syn 1.0.22", -] - -[[package]] -name = "reqwest" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b81e49ddec5109a9dcfc5f2a317ff53377c915e9ae9d4f2fb50914b85614e2" -dependencies = [ - "base64 0.11.0", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "http", - "http-body", - "hyper", - "hyper-tls", - "js-sys", - "lazy_static", - "log", - "mime", - "mime_guess", - "native-tls", - "percent-encoding", - "pin-project-lite", - "serde", - "serde_json", - "serde_urlencoded", - "time", - "tokio", - "tokio-tls", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "winreg", + "syn 1.0.23", ] [[package]] @@ -2647,7 +2621,7 @@ dependencies = [ "libc", "log", "memchr", - "nix 0.13.1", + "nix", "unicode-segmentation", "unicode-width", "utf8parse", @@ -2704,12 +2678,6 @@ dependencies = [ "parking_lot", ] -[[package]] -name = "scoped-tls-hkt" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e9d7eaddb227e8fbaaa71136ae0e1e913ca159b86c7da82f3e8f0044ad3a63" - [[package]] name = "scopeguard" version = "1.1.0" @@ -2755,12 +2723,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -[[package]] -name = "send_wrapper" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" - [[package]] name = "serde" version = "1.0.110" @@ -2778,7 +2740,7 @@ checksum = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984" dependencies = [ "proc-macro2", "quote 1.0.6", - "syn 1.0.22", + "syn 1.0.23", ] [[package]] @@ -2869,37 +2831,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" -[[package]] -name = "smol" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1014c048f35c553bfd0155079a62ab9a0e220f9e39f49244c1d58f825ab84a" -dependencies = [ - "async-task", - "crossbeam", - "futures-io", - "futures-util", - "nix 0.17.0", - "once_cell", - "piper", - "scoped-tls-hkt", - "slab", - "socket2", - "wepoll-binding", -] - -[[package]] -name = "socket2" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "winapi 0.3.8", -] - [[package]] name = "spin" version = "0.5.2" @@ -2921,7 +2852,8 @@ checksum = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3" [[package]] name = "stop-token" version = "0.1.2" -source = "git+https://github.com/dignifiedquire/stop-token#05f56fa8e968fc79230a9a00c49d027b9ecee176" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06855fb7c94d3be9b3a57c4d82dfc8a43bb658fbb3b1dda79de89e748d9eb9dd" dependencies = [ "async-std", "pin-project-lite", @@ -2957,7 +2889,7 @@ dependencies = [ "heck", "proc-macro2", "quote 1.0.6", - "syn 1.0.22", + "syn 1.0.23", ] [[package]] @@ -2966,6 +2898,26 @@ version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c65d530b10ccaeac294f349038a597e435b18fb456aadd0840a623f83b9e941" +[[package]] +name = "surf" +version = "2.0.0-alpha.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d72c302d4a24b2c9d412f1fe5edd892946778d33c423bba087c6c10deeb656ff" +dependencies = [ + "async-std", + "futures 0.3.5", + "http-client", + "http-types", + "log", + "mime", + "mime_guess", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "url", +] + [[package]] name = "syn" version = "0.11.11" @@ -2979,9 +2931,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1425de3c33b0941002740a420b1a906a350b88d08b82b2c8a01035a3f9447bac" +checksum = "95b5f192649e48a5302a13f2feb224df883b98933222369e4b3b0fe2a5447269" dependencies = [ "proc-macro2", "quote 1.0.6", @@ -3005,7 +2957,7 @@ checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" dependencies = [ "proc-macro2", "quote 1.0.6", - "syn 1.0.22", + "syn 1.0.23", "unicode-xid 0.2.0", ] @@ -3044,22 +2996,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5976891d6950b4f68477850b5b9e5aa64d955961466f9e174363f573e54e8ca7" +checksum = "b13f926965ad00595dd129fa12823b04bbf866e9085ab0a5f2b05b850fbfc344" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab81dbd1cd69cd2ce22ecfbdd3bdb73334ba25350649408cc6c085f46d89573d" +checksum = "893582086c2f98cde18f906265a65b5030a074b1046c674ae898be6519a7f479" dependencies = [ "proc-macro2", "quote 1.0.6", - "syn 1.0.22", + "syn 1.0.23", ] [[package]] @@ -3091,45 +3043,14 @@ dependencies = [ ] [[package]] -name = "tokio" -version = "0.2.21" +name = "tokio-io" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d099fa27b9702bed751524694adbe393e18b36b204da91eb1cbbbbb4a5ee2d58" +checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674" dependencies = [ - "bytes", - "fnv", - "futures-core", - "iovec", - "lazy_static", - "memchr", - "mio", - "num_cpus", - "pin-project-lite", - "slab", -] - -[[package]] -name = "tokio-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a70f4fcd7b3b24fb194f837560168208f669ca8cb70d0c4b862944452396343" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", + "bytes 0.4.12", + "futures 0.1.29", "log", - "pin-project-lite", - "tokio", ] [[package]] @@ -3141,24 +3062,12 @@ dependencies = [ "serde", ] -[[package]] -name = "tower-service" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860" - [[package]] name = "traitobject" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" -[[package]] -name = "try-lock" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" - [[package]] name = "try_from" version = "0.3.2" @@ -3315,16 +3224,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "want" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" -dependencies = [ - "log", - "try-lock", -] - [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -3338,8 +3237,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c7d40d09cdbf0f4895ae58cf57d92e1e57a9dd8ed2e8390514b54a47cc5551" dependencies = [ "cfg-if", - "serde", - "serde_json", "wasm-bindgen-macro", ] @@ -3354,7 +3251,7 @@ dependencies = [ "log", "proc-macro2", "quote 1.0.6", - "syn 1.0.22", + "syn 1.0.23", "wasm-bindgen-shared", ] @@ -3388,7 +3285,7 @@ checksum = "8eb197bd3a47553334907ffd2f16507b4f4f01bbec3ac921a7719e0decdfe72a" dependencies = [ "proc-macro2", "quote 1.0.6", - "syn 1.0.22", + "syn 1.0.23", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3409,25 +3306,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "wepoll-binding" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7511014d92edaa2cba607fd6dc62974915bebc3e16f91f911a4bb240b4a3a68c" -dependencies = [ - "bitflags", - "wepoll-sys", -] - -[[package]] -name = "wepoll-sys" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9082a777aed991f6769e2b654aa0cb29f1c3d615daf009829b07b66c7aff6a24" -dependencies = [ - "cc", -] - [[package]] name = "winapi" version = "0.2.8" @@ -3471,15 +3349,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "winreg" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" -dependencies = [ - "winapi 0.3.8", -] - [[package]] name = "winutil" version = "0.1.1" @@ -3527,6 +3396,6 @@ checksum = "de251eec69fc7c1bc3923403d18ececb929380e016afe103da75f396704f8ca2" dependencies = [ "proc-macro2", "quote 1.0.6", - "syn 1.0.22", + "syn 1.0.23", "synstructure", ] diff --git a/Cargo.toml b/Cargo.toml index 8b59a2df1..5d3f58923 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,15 +17,15 @@ hex = "0.4.0" sha2 = "0.8.0" rand = "0.7.0" smallvec = "1.0.0" -reqwest = { version = "0.10.0", features = ["blocking", "json"] } +surf = { version = "2.0.0-alpha.2", default-features = false, features = ["h1-client"] } num-derive = "0.3.0" num-traits = "0.2.6" -async-smtp = { git = "https://github.com/async-email/async-smtp", version = "0.3" } +async-smtp = { version = "0.3" } email = { git = "https://github.com/deltachat/rust-email", branch = "master" } lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" } -async-imap = { git = "https://github.com/async-email/async-imap" } -async-native-tls = { git = "https://github.com/async-email/async-native-tls", version = "0.3.1" } -async-std = { version = "1.6.0-beta.2", features = ["unstable"] } +async-imap = { git = "https://github.com/async-email/async-imap", rev = "1e8fdd86dbc99b6d92ab4667b426092060fe8b16" } +async-native-tls = { version = "0.3.1" } +async-std = { version = "1.5.0", features = ["unstable"] } base64 = "0.11" charset = "0.1" percent-encoding = "2.0" @@ -50,7 +50,7 @@ escaper = "0.1.0" bitflags = "1.1.0" debug_stub_derive = "0.3.0" sanitize-filename = "0.2.1" -stop-token = { git = "https://github.com/dignifiedquire/stop-token", version = "0.1.1", features = ["unstable"] } +stop-token = { version = "0.1.1", features = ["unstable"] } mailparse = "0.12.0" encoded-words = { git = "https://github.com/async-email/encoded-words", branch="master" } native-tls = "0.2.3" @@ -65,13 +65,14 @@ rustyline = { version = "4.1.0", optional = true } ansi_term = { version = "0.12.1", optional = true } async-trait = "0.1.31" crossbeam-channel = "0.4.2" +url = "2.1.1" [dev-dependencies] tempfile = "3.0" pretty_assertions = "0.6.1" pretty_env_logger = "0.3.0" proptest = "0.9.4" -async-std = { version = "1.6.0-beta.2", features = ["unstable", "attributes"] } +async-std = { version = "1.5.0", features = ["unstable", "attributes"] } [workspace] members = [ @@ -93,5 +94,5 @@ required-features = ["repl"] default = ["nightly"] internals = [] repl = ["internals", "rustyline", "log", "pretty_env_logger", "ansi_term"] -vendored = ["async-native-tls/vendored", "reqwest/native-tls-vendored", "async-smtp/native-tls-vendored"] +vendored = ["async-native-tls/vendored", "async-smtp/native-tls-vendored"] nightly = ["pgp/nightly"] diff --git a/deltachat-ffi/Cargo.toml b/deltachat-ffi/Cargo.toml index a49ec858f..22af8af1d 100644 --- a/deltachat-ffi/Cargo.toml +++ b/deltachat-ffi/Cargo.toml @@ -20,7 +20,7 @@ libc = "0.2" human-panic = "1.0.1" num-traits = "0.2.6" serde_json = "1.0" -async-std = "1.6.0-beta.2" +async-std = "1.5.0" anyhow = "1.0.28" thiserror = "1.0.14" diff --git a/src/configure/auto_mozilla.rs b/src/configure/auto_mozilla.rs index dbadc9543..984012924 100644 --- a/src/configure/auto_mozilla.rs +++ b/src/configure/auto_mozilla.rs @@ -94,12 +94,12 @@ fn parse_xml(in_emailaddr: &str, xml_raw: &str) -> Result { } } -pub fn moz_autoconfigure( +pub async fn moz_autoconfigure( context: &Context, url: &str, param_in: &LoginParam, ) -> Result { - let xml_raw = read_url(context, url)?; + let xml_raw = read_url(context, url).await?; let res = parse_xml(¶m_in.addr, &xml_raw); if let Err(err) = &res { diff --git a/src/configure/auto_outlook.rs b/src/configure/auto_outlook.rs index 96b22e2ab..638ae2505 100644 --- a/src/configure/auto_outlook.rs +++ b/src/configure/auto_outlook.rs @@ -112,7 +112,7 @@ fn parse_xml(xml_raw: &str) -> Result { Ok(res) } -pub fn outlk_autodiscover( +pub async fn outlk_autodiscover( context: &Context, url: &str, _param_in: &LoginParam, @@ -120,7 +120,7 @@ pub fn outlk_autodiscover( let mut url = url.to_string(); /* Follow up to 10 xml-redirects (http-redirects are followed in read_url() */ for _i in 0..10 { - let xml_raw = read_url(context, &url)?; + let xml_raw = read_url(context, &url).await?; let res = parse_xml(&xml_raw); if let Err(err) = &res { warn!(context, "{}", err); diff --git a/src/configure/mod.rs b/src/configure/mod.rs index 619e359a0..81bf319b9 100644 --- a/src/configure/mod.rs +++ b/src/configure/mod.rs @@ -248,7 +248,7 @@ async fn exec_step( "https://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}", param_domain, param_addr_urlencoded ); - *param_autoconfig = moz_autoconfigure(ctx, &url, ¶m).ok(); + *param_autoconfig = moz_autoconfigure(ctx, &url, ¶m).await.ok(); } } 6 => { @@ -259,7 +259,7 @@ async fn exec_step( "https://{}/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress={}", param_domain, param_addr_urlencoded ); - *param_autoconfig = moz_autoconfigure(ctx, &url, ¶m).ok(); + *param_autoconfig = moz_autoconfigure(ctx, &url, ¶m).await.ok(); } } /* Outlook section start ------------- */ @@ -268,7 +268,7 @@ async fn exec_step( progress!(ctx, 310); if param_autoconfig.is_none() { let url = format!("https://{}/autodiscover/autodiscover.xml", param_domain); - *param_autoconfig = outlk_autodiscover(ctx, &url, ¶m).ok(); + *param_autoconfig = outlk_autodiscover(ctx, &url, ¶m).await.ok(); } } 8 => { @@ -278,7 +278,7 @@ async fn exec_step( "https://{}{}/autodiscover/autodiscover.xml", "autodiscover.", param_domain ); - *param_autoconfig = outlk_autodiscover(ctx, &url, ¶m).ok(); + *param_autoconfig = outlk_autodiscover(ctx, &url, ¶m).await.ok(); } } /* ----------- Outlook section end */ @@ -289,7 +289,7 @@ async fn exec_step( "http://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}", param_domain, param_addr_urlencoded ); - *param_autoconfig = moz_autoconfigure(ctx, &url, ¶m).ok(); + *param_autoconfig = moz_autoconfigure(ctx, &url, ¶m).await.ok(); } } 10 => { @@ -300,7 +300,7 @@ async fn exec_step( "http://{}/.well-known/autoconfig/mail/config-v1.1.xml", param_domain ); - *param_autoconfig = moz_autoconfigure(ctx, &url, ¶m).ok(); + *param_autoconfig = moz_autoconfigure(ctx, &url, ¶m).await.ok(); } } /* B. If we have no configuration yet, search configuration in Thunderbird's centeral database */ @@ -309,7 +309,7 @@ async fn exec_step( if param_autoconfig.is_none() { /* always SSL for Thunderbird's database */ let url = format!("https://autoconfig.thunderbird.net/v1.1/{}", param_domain); - *param_autoconfig = moz_autoconfigure(ctx, &url, ¶m).ok(); + *param_autoconfig = moz_autoconfigure(ctx, &url, ¶m).await.ok(); } } /* C. Do we have any autoconfig result? diff --git a/src/configure/read_url.rs b/src/configure/read_url.rs index 498f8e0c8..096b7b218 100644 --- a/src/configure/read_url.rs +++ b/src/configure/read_url.rs @@ -3,17 +3,13 @@ use crate::context::Context; #[derive(Debug, thiserror::Error)] pub enum Error { #[error("URL request error")] - GetError(#[from] reqwest::Error), + GetError(surf::Error), } -pub fn read_url(context: &Context, url: &str) -> Result { +pub async fn read_url(context: &Context, url: &str) -> Result { info!(context, "Requesting URL {}", url); - match reqwest::blocking::Client::new() - .get(url) - .send() - .and_then(|res| res.text()) - { + match surf::get(url).recv_string().await { Ok(res) => Ok(res), Err(err) => { info!(context, "Can\'t read URL {}", url); diff --git a/src/oauth2.rs b/src/oauth2.rs index 89354f651..3dd22874f 100644 --- a/src/oauth2.rs +++ b/src/oauth2.rs @@ -161,10 +161,7 @@ pub async fn dc_get_oauth2_access_token( } // ... and POST - let response = reqwest::blocking::Client::new() - .post(post_url) - .form(&post_param) - .send(); + let response = surf::post(post_url).body_form(&post_param); if response.is_err() { warn!( context, @@ -172,19 +169,8 @@ pub async fn dc_get_oauth2_access_token( ); return None; } - let response = response.unwrap(); - if !response.status().is_success() { - warn!( - context, - "Unsuccessful response when calling OAuth2 at {}: {:?}", - token_url, - response.status() - ); - return None; - } - // generate new token: parse returned json - let parsed: reqwest::Result = response.json(); + let parsed: Result = response.unwrap().recv_json().await; if parsed.is_err() { warn!( context, @@ -192,7 +178,6 @@ pub async fn dc_get_oauth2_access_token( ); return None; } - println!("response: {:?}", &parsed); // update refresh_token if given, typically on the first round, but we update it later as well. let response = parsed.unwrap(); @@ -260,12 +245,12 @@ pub async fn dc_get_oauth2_addr( if let Some(access_token) = dc_get_oauth2_access_token(context, addr.as_ref(), code.as_ref(), false).await { - let addr_out = oauth2.get_addr(context, access_token); + let addr_out = oauth2.get_addr(context, access_token).await; if addr_out.is_none() { // regenerate if let Some(access_token) = dc_get_oauth2_access_token(context, addr, code, true).await { - oauth2.get_addr(context, access_token) + oauth2.get_addr(context, access_token).await } else { None } @@ -295,7 +280,7 @@ impl Oauth2 { } } - fn get_addr(&self, context: &Context, access_token: impl AsRef) -> Option { + async fn get_addr(&self, context: &Context, access_token: impl AsRef) -> Option { let userinfo_url = self.get_userinfo.unwrap_or_else(|| ""); let userinfo_url = replace_in_uri(&userinfo_url, "$ACCESS_TOKEN", access_token); @@ -306,41 +291,25 @@ impl Oauth2 { // "verified_email": true, // "picture": "https://lh4.googleusercontent.com/-Gj5jh_9R0BY/AAAAAAAAAAI/AAAAAAAAAAA/IAjtjfjtjNA/photo.jpg" // } - let response = reqwest::blocking::Client::new().get(&userinfo_url).send(); + let response: Result, surf::Error> = + surf::get(userinfo_url).recv_json().await; if response.is_err() { warn!(context, "Error getting userinfo: {:?}", response); return None; } - let response = response.unwrap(); - if !response.status().is_success() { - warn!(context, "Error getting userinfo: {:?}", response.status()); - return None; - } - let parsed: reqwest::Result> = response.json(); - if parsed.is_err() { - warn!( - context, - "Failed to parse userinfo JSON response: {:?}", parsed - ); - return None; - } - if let Ok(response) = parsed { - // CAVE: serde_json::Value.as_str() removes the quotes of json-strings - // but serde_json::Value.to_string() does not! - if let Some(addr) = response.get("email") { - if let Some(s) = addr.as_str() { - Some(s.to_string()) - } else { - warn!(context, "E-mail in userinfo is not a string: {}", addr); - None - } + let parsed = response.unwrap(); + // CAVE: serde_json::Value.as_str() removes the quotes of json-strings + // but serde_json::Value.to_string() does not! + if let Some(addr) = parsed.get("email") { + if let Some(s) = addr.as_str() { + Some(s.to_string()) } else { - warn!(context, "E-mail missing in userinfo."); + warn!(context, "E-mail in userinfo is not a string: {}", addr); None } } else { - warn!(context, "Failed to parse userinfo."); + warn!(context, "E-mail missing in userinfo."); None } } diff --git a/src/qr.rs b/src/qr.rs index fa2783823..ff8487232 100644 --- a/src/qr.rs +++ b/src/qr.rs @@ -2,7 +2,6 @@ use lazy_static::lazy_static; use percent_encoding::percent_decode_str; -use reqwest::Url; use serde::Deserialize; use crate::chat; @@ -193,7 +192,7 @@ fn decode_account(_context: &Context, qr: &str) -> Lot { let mut lot = Lot::new(); - if let Ok(url) = Url::parse(payload) { + if let Ok(url) = url::Url::parse(payload) { if url.scheme() == "https" { lot.state = LotState::QrAccount; lot.text1 = url.host_str().map(|x| x.to_string()); @@ -221,25 +220,12 @@ struct CreateAccountResponse { pub async fn set_config_from_qr(context: &Context, qr: &str) -> Result<(), Error> { let url_str = &qr[DCACCOUNT_SCHEME.len()..]; - let response = reqwest::blocking::Client::new().post(url_str).send(); + let response: Result = + surf::post(url_str).recv_json().await; if response.is_err() { bail!("Cannot create account, request to {} failed", url_str); } - let response = response.unwrap(); - if !response.status().is_success() { - bail!("Request to {} unsuccessful: {:?}", url_str, response); - } - - let parsed: reqwest::Result = response.json(); - if parsed.is_err() { - bail!( - "Failed to parse JSON response from {}: error: {:?}", - url_str, - parsed - ); - } - println!("response: {:?}", &parsed); - let parsed = parsed.unwrap(); + let parsed = response.unwrap(); context .set_config(Config::Addr, Some(&parsed.email)) diff --git a/src/scheduler.rs b/src/scheduler.rs index 7573f9c41..be0ac7f3d 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -341,12 +341,10 @@ impl Scheduler { // wait for all loops to be started inbox_start_recv .recv() - .try_join(mvbox_start_recv.recv()) - .try_join(sentbox_start_recv.recv()) - .try_join(smtp_start_recv.recv()) - .await - .map(|_| ()) - .unwrap_or_else(|err| error!(ctx, "failed to start scheduler: {}", err)); + .join(mvbox_start_recv.recv()) + .join(sentbox_start_recv.recv()) + .join(smtp_start_recv.recv()) + .await; info!(ctx, "scheduler is running"); } @@ -384,36 +382,28 @@ impl Scheduler { async fn interrupt_inbox(&self) { match self { - Scheduler::Running { ref inbox, .. } => { - inbox.interrupt().await.ok(); - } + Scheduler::Running { ref inbox, .. } => inbox.interrupt().await, _ => {} } } async fn interrupt_mvbox(&self) { match self { - Scheduler::Running { ref mvbox, .. } => { - mvbox.interrupt().await.ok(); - } + Scheduler::Running { ref mvbox, .. } => mvbox.interrupt().await, _ => {} } } async fn interrupt_sentbox(&self) { match self { - Scheduler::Running { ref sentbox, .. } => { - sentbox.interrupt().await.ok(); - } + Scheduler::Running { ref sentbox, .. } => sentbox.interrupt().await, _ => {} } } async fn interrupt_smtp(&self) { match self { - Scheduler::Running { ref smtp, .. } => { - smtp.interrupt().await.ok(); - } + Scheduler::Running { ref smtp, .. } => smtp.interrupt().await, _ => {} } } @@ -492,12 +482,14 @@ impl ConnectionState { // Trigger shutdown of the run loop. self.stop_sender.send(()).await; // Wait for a notification that the run loop has been shutdown. - self.shutdown_receiver.recv().await.ok(); + self.shutdown_receiver.recv().await; } - async fn interrupt(&self) -> Result<(), async_std::sync::TrySendError<()>> { - // Use try_send to avoid blocking on interrupts. - self.idle_interrupt_sender.try_send(()) + async fn interrupt(&self) { + if !self.idle_interrupt_sender.is_full() { + // Use try_send to avoid blocking on interrupts. + self.idle_interrupt_sender.send(()).await; + } } } @@ -531,8 +523,8 @@ impl SmtpConnectionState { } /// Interrupt any form of idle. - async fn interrupt(&self) -> Result<(), async_std::sync::TrySendError<()>> { - self.state.interrupt().await + async fn interrupt(&self) { + self.state.interrupt().await; } /// Shutdown this connection completely. @@ -579,8 +571,8 @@ impl ImapConnectionState { } /// Interrupt any form of idle. - async fn interrupt(&self) -> Result<(), async_std::sync::TrySendError<()>> { - self.state.interrupt().await + async fn interrupt(&self) { + self.state.interrupt().await; } /// Shutdown this connection completely. From d1f9563e1f735e4618c2541291eb03c6e106cf28 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Fri, 22 May 2020 11:37:03 +0200 Subject: [PATCH 073/118] remove tracking of current sql query this will not work like this anymore with async --- Cargo.lock | 25 ----------------- Cargo.toml | 1 - src/sql.rs | 82 ++++++++++-------------------------------------------- 3 files changed, 15 insertions(+), 93 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ac6866432..de71a318f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -796,7 +796,6 @@ dependencies = [ "surf", "tempfile", "thiserror", - "thread-local-object", "url", ] @@ -3014,15 +3013,6 @@ dependencies = [ "syn 1.0.23", ] -[[package]] -name = "thread-local-object" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da3caa820d0308c84c8654f6cafd81cc3195d45433311cbe22fcf44fc8be071" -dependencies = [ - "unsafe-any", -] - [[package]] name = "thread_local" version = "1.0.1" @@ -3062,12 +3052,6 @@ dependencies = [ "serde", ] -[[package]] -name = "traitobject" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" - [[package]] name = "try_from" version = "0.3.2" @@ -3145,15 +3129,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" -[[package]] -name = "unsafe-any" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30360d7979f5e9c6e6cea48af192ea8fab4afb3cf72597154b8f08935bc9c7f" -dependencies = [ - "traitobject", -] - [[package]] name = "url" version = "2.1.1" diff --git a/Cargo.toml b/Cargo.toml index 5d3f58923..802a9daf6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,6 @@ r2d2_sqlite = "0.15.0" r2d2 = "0.8.5" strum = "0.16.0" strum_macros = "0.16.0" -thread-local-object = "0.1.0" backtrace = "0.3.33" byteorder = "1.3.1" itertools = "0.8.0" diff --git a/src/sql.rs b/src/sql.rs index 47c2950ac..c20e6bd89 100644 --- a/src/sql.rs +++ b/src/sql.rs @@ -1,14 +1,13 @@ //! # SQLite wrapper use async_std::prelude::*; -use async_std::sync::{Arc, RwLock}; +use async_std::sync::RwLock; use std::collections::HashSet; use std::path::Path; use std::time::Duration; use rusqlite::{Connection, Error as SqlError, OpenFlags}; -use thread_local_object::ThreadLocal; use crate::chat::{update_device_icon, update_saved_messages_icon}; use crate::constants::{ShowEmails, DC_CHAT_ID_TRASH}; @@ -53,15 +52,12 @@ pub type Result = std::result::Result; #[derive(DebugStub)] pub struct Sql { pool: RwLock>>, - #[debug_stub = "ThreadLocal"] - in_use: Arc>, } impl Default for Sql { fn default() -> Self { Self { pool: RwLock::new(None), - in_use: Arc::new(ThreadLocal::new()), } } } @@ -77,7 +73,6 @@ impl Sql { pub async fn close(&self) { let _ = self.pool.write().await.take(); - self.in_use.remove(); // drop closes the connection } @@ -100,13 +95,9 @@ impl Sql { sql: S, params: Vec<&dyn crate::ToSql>, ) -> Result { - self.start_stmt(sql.as_ref()); - let res = { let conn = self.get_conn().await?; - let res = conn.execute(sql.as_ref(), params); - self.in_use.remove(); - res + conn.execute(sql.as_ref(), params) }; res.map_err(Into::into) @@ -126,18 +117,12 @@ impl Sql { F: FnMut(&rusqlite::Row) -> rusqlite::Result, G: FnMut(rusqlite::MappedRows) -> Result, { - self.start_stmt(sql.as_ref().to_string()); let sql = sql.as_ref(); - let res = { - let conn = self.get_conn().await?; - let mut stmt = conn.prepare(sql)?; - let res = stmt.query_map(¶ms, f)?; - self.in_use.remove(); - g(res) - }; - - res + let conn = self.get_conn().await?; + let mut stmt = conn.prepare(sql)?; + let res = stmt.query_map(¶ms, f)?; + g(res) } pub async fn get_conn( @@ -161,10 +146,7 @@ impl Sql { let pool = lock.as_ref().ok_or_else(|| Error::SqlNoConnection)?; let conn = pool.get()?; - let res = g(conn); - self.in_use.remove(); - - res + g(conn) } pub async fn with_conn_async(&self, mut g: G) -> Result @@ -175,25 +157,17 @@ impl Sql { let lock = self.pool.read().await; let pool = lock.as_ref().ok_or_else(|| Error::SqlNoConnection)?; - let res = { - let conn = pool.get()?; - let res = g(conn).await; - self.in_use.remove(); - res - }; - res + let conn = pool.get()?; + g(conn).await } /// Return `true` if a query in the SQL statement it executes returns one or more /// rows and false if the SQL returns an empty set. pub async fn exists(&self, sql: &str, params: Vec<&dyn crate::ToSql>) -> Result { - self.start_stmt(sql.to_string()); let res = { let conn = self.get_conn().await?; let mut stmt = conn.prepare(sql)?; - let res = stmt.exists(¶ms); - self.in_use.remove(); - res + stmt.exists(¶ms) }; res.map_err(Into::into) @@ -209,20 +183,16 @@ impl Sql { where F: FnOnce(&rusqlite::Row) -> rusqlite::Result, { - self.start_stmt(sql.as_ref().to_string()); let sql = sql.as_ref(); let res = { let conn = self.get_conn().await?; - let res = conn.query_row(sql, params, f); - self.in_use.remove(); - res + conn.query_row(sql, params, f) }; res.map_err(Into::into) } pub async fn table_exists(&self, name: impl AsRef) -> Result { - self.start_stmt("table_exists"); let name = name.as_ref().to_string(); self.with_conn(move |conn| { let mut exists = false; @@ -317,13 +287,13 @@ impl Sql { if exists { self.execute( "UPDATE config SET value=? WHERE keyname=?;", - paramsv![value.to_string(), key.to_string()], + paramsv![(*value).to_string(), key.to_string()], ) .await } else { self.execute( "INSERT INTO config (keyname, value) VALUES (?, ?);", - paramsv![key.to_string(), value.to_string()], + paramsv![key.to_string(), (*value).to_string()], ) .await } @@ -405,20 +375,6 @@ impl Sql { .and_then(|r| r.parse().ok()) } - pub fn start_stmt(&self, stmt: impl AsRef) { - if let Some(query) = self.in_use.get_cloned() { - let bt = backtrace::Backtrace::new(); - eprintln!("old query: {}", query); - eprintln!("Connection is already used from this thread: {:?}", bt); - panic!( - "Connection is already used from this thread: trying to execute {}", - stmt.as_ref() - ); - } - - self.in_use.set(stmt.as_ref().to_string()); - } - /// Alternative to sqlite3_last_insert_rowid() which MUST NOT be used due to race conditions, see comment above. /// the ORDER BY ensures, this function always returns the most recent id, /// eg. if a Message-ID is split into different messages. @@ -429,13 +385,9 @@ impl Sql { field: impl AsRef, value: impl AsRef, ) -> Result { - self.start_stmt("get rowid".to_string()); - let res = { let mut conn = self.get_conn().await?; - let res = get_rowid(&mut conn, table, field, value); - self.in_use.remove(); - res + get_rowid(&mut conn, table, field, value) }; res.map_err(Into::into) @@ -450,13 +402,9 @@ impl Sql { field2: impl AsRef, value2: i32, ) -> Result { - self.start_stmt("get rowid2".to_string()); - let res = { let mut conn = self.get_conn().await?; - let res = get_rowid2(&mut conn, table, field, value, field2, value2); - self.in_use.remove(); - res + get_rowid2(&mut conn, table, field, value, field2, value2) }; res.map_err(Into::into) From 28ef5164ceb6f02fb1025bd426b6ec4c5285af80 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Fri, 22 May 2020 11:37:12 +0200 Subject: [PATCH 074/118] happy clippy --- src/chat.rs | 4 ++-- src/configure/mod.rs | 1 + src/param.rs | 1 + src/scheduler.rs | 20 ++++++++------------ src/securejoin.rs | 4 ++-- 5 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/chat.rs b/src/chat.rs index 546f9737b..4caa095ab 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -1295,8 +1295,8 @@ pub async fn get_by_contact_id(context: &Context, contact_id: u32) -> Result( - context: &'a Context, +pub async fn prepare_msg( + context: &Context, chat_id: ChatId, msg: &mut Message, ) -> Result { diff --git a/src/configure/mod.rs b/src/configure/mod.rs index 81bf319b9..bb981f6d4 100644 --- a/src/configure/mod.rs +++ b/src/configure/mod.rs @@ -166,6 +166,7 @@ impl Context { } } +#[allow(clippy::too_many_arguments)] async fn exec_step( ctx: &Context, imap: &mut Imap, diff --git a/src/param.rs b/src/param.rs index 3fc64e346..7d72adad9 100644 --- a/src/param.rs +++ b/src/param.rs @@ -275,6 +275,7 @@ impl Params { /// created without copying if the path already referes to a valid /// blob. If so a [BlobObject] will be returned regardless of the /// `create` argument. + #[allow(clippy::needless_lifetimes)] pub async fn get_blob<'a>( &self, key: Param, diff --git a/src/scheduler.rs b/src/scheduler.rs index be0ac7f3d..b9f9b5220 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -381,30 +381,26 @@ impl Scheduler { } async fn interrupt_inbox(&self) { - match self { - Scheduler::Running { ref inbox, .. } => inbox.interrupt().await, - _ => {} + if let Scheduler::Running { ref inbox, .. } = self { + inbox.interrupt().await; } } async fn interrupt_mvbox(&self) { - match self { - Scheduler::Running { ref mvbox, .. } => mvbox.interrupt().await, - _ => {} + if let Scheduler::Running { ref mvbox, .. } = self { + mvbox.interrupt().await; } } async fn interrupt_sentbox(&self) { - match self { - Scheduler::Running { ref sentbox, .. } => sentbox.interrupt().await, - _ => {} + if let Scheduler::Running { ref sentbox, .. } = self { + sentbox.interrupt().await; } } async fn interrupt_smtp(&self) { - match self { - Scheduler::Running { ref smtp, .. } => smtp.interrupt().await, - _ => {} + if let Scheduler::Running { ref smtp, .. } = self { + smtp.interrupt().await; } } diff --git a/src/securejoin.rs b/src/securejoin.rs index aab4dab5c..8e93fb6d5 100644 --- a/src/securejoin.rs +++ b/src/securejoin.rs @@ -184,7 +184,7 @@ async fn cleanup( /// Take a scanned QR-code and do the setup-contact/join-group handshake. /// See the ffi-documentation for more details. pub async fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId { - if let Err(_) = context.alloc_ongoing().await { + if context.alloc_ongoing().await.is_err() { return cleanup(&context, ChatId::new(0), false, false).await; } @@ -344,7 +344,7 @@ async fn send_handshake_msg( chat::send_msg(context, contact_chat_id, &mut msg) .await - .map_err(|err| HandshakeError::MsgSendFailed(err))?; + .map_err(HandshakeError::MsgSendFailed)?; Ok(()) } From da7db04c0e88185ff11ad781301ed12bf322a498 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Fri, 22 May 2020 11:48:40 +0200 Subject: [PATCH 075/118] example: happy clippy --- examples/repl/cmdline.rs | 12 ++++++------ examples/repl/main.rs | 12 ++---------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/examples/repl/cmdline.rs b/examples/repl/cmdline.rs index ca0a74a7e..dc4659311 100644 --- a/examples/repl/cmdline.rs +++ b/examples/repl/cmdline.rs @@ -212,7 +212,7 @@ async fn log_msg(context: &Context, prefix: impl AsRef, msg: &Message) { ); } -async fn log_msglist(context: &Context, msglist: &Vec) -> Result<(), Error> { +async fn log_msglist(context: &Context, msglist: &[MsgId]) -> Result<(), Error> { let mut lines_out = 0; for &msg_id in msglist { if msg_id.is_daymarker() { @@ -240,8 +240,8 @@ async fn log_msglist(context: &Context, msglist: &Vec) -> Result<(), Erro Ok(()) } -async fn log_contactlist(context: &Context, contacts: &Vec) { - let mut contacts = contacts.clone(); +async fn log_contactlist(context: &Context, contacts: &[u32]) { + let mut contacts = contacts.to_vec(); if !contacts.contains(&1) { contacts.push(1); } @@ -862,7 +862,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu print!(", {}", data); } } - print!("\n"); + println!(); } "archive" | "unarchive" | "pin" | "unpin" => { ensure!(!arg1.is_empty(), "Argument missing."); @@ -1049,8 +1049,8 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu "estimatedeletion" => { ensure!(!arg1.is_empty(), "Argument missing"); let seconds = arg1.parse()?; - let device_cnt = message::estimate_deletion_cnt(context, false, seconds)?; - let server_cnt = message::estimate_deletion_cnt(context, true, seconds)?; + let device_cnt = message::estimate_deletion_cnt(&context, false, seconds).await?; + let server_cnt = message::estimate_deletion_cnt(&context, true, seconds).await?; println!( "estimated count of messages older than {} seconds:\non device: {}\non server: {}", seconds, device_cnt, server_cnt diff --git a/examples/repl/main.rs b/examples/repl/main.rs index 9df98fdc4..04d0fe105 100644 --- a/examples/repl/main.rs +++ b/examples/repl/main.rs @@ -6,10 +6,6 @@ #[macro_use] extern crate deltachat; -#[macro_use] -extern crate lazy_static; -#[macro_use] -extern crate rusqlite; use std::borrow::Cow::{self, Borrowed, Owned}; use std::io::{self, Write}; @@ -282,12 +278,8 @@ async fn start(args: Vec) -> Result<(), Error> { let ctx = context.clone(); async_std::task::spawn(async move { loop { - if ctx.has_next_event() { - if let Ok(event) = ctx.get_next_event() { - receive_event(event); - } - } else { - async_std::task::sleep(std::time::Duration::from_millis(50)).await; + if let Ok(event) = ctx.get_next_event() { + receive_event(event); } } }); From 7c8758dc26f7dfc4a3825b87ff3ab86623fc3e21 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Fri, 22 May 2020 12:24:52 +0200 Subject: [PATCH 076/118] fix ordering issues with test_forward_increation --- python/tests/test_increation.py | 42 +++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/python/tests/test_increation.py b/python/tests/test_increation.py index 34b9050b0..c6356d6a3 100644 --- a/python/tests/test_increation.py +++ b/python/tests/test_increation.py @@ -9,15 +9,29 @@ from filecmp import cmp from deltachat import const +def wait_msg_delivered(account, msg_list): + while msg_list: + ev = account._evtracker.get_matching("DC_EVENT_MSG_DELIVERED") + msg_list.remove((ev.data1, ev.data2)) + + +def get_msgs_changed(account): + while 1: + ev = account._evtracker.get_matching("DC_EVENT_MSGS_CHANGED") + yield (ev.data1, ev.data2) + + def wait_msgs_changed(account, chat_id, msg_id=None): account.log("waiting for chat_id={} msg_id={}".format(chat_id, msg_id)) while 1: ev = account._evtracker.get_matching("DC_EVENT_MSGS_CHANGED") if ev.data1 != chat_id: account.log("waiting got mismatched DC_EVENT_MSGS_CHANGED") + account.log("expect chat_id={} got data1={}".format(chat_id, ev.data1)) continue if msg_id is not None: assert ev.data2 == msg_id + account.log("WAIT -> FOUND chat_id={} msg_id={}".format(chat_id, msg_id)) return ev.data2 @@ -85,20 +99,24 @@ class TestOnlineInCreation: shutil.copyfile(orig, path) chat.send_prepared(prepared_original) assert prepared_original.is_out_pending() or prepared_original.is_out_delivered() - wait_msgs_changed(ac1, chat.id, prepared_original.id) - lp.sec("expect the forwarded message to be sent now too") - wait_msgs_changed(ac1, chat2.id, forwarded_id) - fwd_msg = ac1.get_message_by_id(forwarded_id) - assert fwd_msg.is_out_pending() or fwd_msg.is_out_delivered() + lp.sec("check that both forwarded and original message are proper.") + forwarded, original = False, False + for chat_id, msg_id in get_msgs_changed(ac1): + if chat_id == chat2.id and msg_id == forwarded_id: + forwarded = True + fwd_msg = ac1.get_message_by_id(forwarded_id) + assert fwd_msg.is_out_pending() or fwd_msg.is_out_delivered() + elif chat_id == chat.id and msg_id == prepared_original.id: + original = True + if forwarded and original: + break - lp.sec("wait for the messages to be delivered to SMTP") - ev = ac1._evtracker.get_matching("DC_EVENT_MSG_DELIVERED") - assert ev.data1 == chat.id - assert ev.data2 == prepared_original.id - ev = ac1._evtracker.get_matching("DC_EVENT_MSG_DELIVERED") - assert ev.data1 == chat2.id - assert ev.data2 == forwarded_id + lp.sec("wait for both messages to be delivered to SMTP") + wait_msg_delivered(ac1, [ + (chat2.id, forwarded_id), + (chat.id, prepared_original.id) + ]) lp.sec("wait1 for original or forwarded messages to arrive") ev1 = ac2._evtracker.get_matching("DC_EVENT_MSGS_CHANGED") From f97538a399ae0da8913664860cba10bda09be0cc Mon Sep 17 00:00:00 2001 From: holger krekel Date: Fri, 22 May 2020 12:46:12 +0200 Subject: [PATCH 077/118] slightly refine increation tests wrt to ordering --- python/tests/test_increation.py | 57 +++++++++++++-------------------- 1 file changed, 23 insertions(+), 34 deletions(-) diff --git a/python/tests/test_increation.py b/python/tests/test_increation.py index c6356d6a3..99d49771d 100644 --- a/python/tests/test_increation.py +++ b/python/tests/test_increation.py @@ -10,29 +10,27 @@ from deltachat import const def wait_msg_delivered(account, msg_list): + """ wait for one or more MSG_DELIVERED events to match msg_list contents. """ + msg_list = list(msg_list) while msg_list: ev = account._evtracker.get_matching("DC_EVENT_MSG_DELIVERED") msg_list.remove((ev.data1, ev.data2)) -def get_msgs_changed(account): - while 1: +def wait_msgs_changed(account, msgs_list): + """ wait for one or more MSGS_CHANGED events to match msgs_list contents. """ + account.log("waiting for msgs_list={}".format(msgs_list)) + msgs_list = list(msgs_list) + while msgs_list: ev = account._evtracker.get_matching("DC_EVENT_MSGS_CHANGED") - yield (ev.data1, ev.data2) - - -def wait_msgs_changed(account, chat_id, msg_id=None): - account.log("waiting for chat_id={} msg_id={}".format(chat_id, msg_id)) - while 1: - ev = account._evtracker.get_matching("DC_EVENT_MSGS_CHANGED") - if ev.data1 != chat_id: - account.log("waiting got mismatched DC_EVENT_MSGS_CHANGED") - account.log("expect chat_id={} got data1={}".format(chat_id, ev.data1)) - continue - if msg_id is not None: - assert ev.data2 == msg_id - account.log("WAIT -> FOUND chat_id={} msg_id={}".format(chat_id, msg_id)) - return ev.data2 + for i, (data1, data2) in enumerate(msgs_list): + if ev.data1 == data1: + if data2 is None or ev.data2 == data2: + del msgs_list[i] + break + else: + account.log("waiting mismatch data1={} data2={}".format(data1, data2)) + return ev.data1, ev.data2 class TestOnlineInCreation: @@ -69,7 +67,7 @@ class TestOnlineInCreation: c2 = ac1.create_contact(email=ac2.get_config("addr")) chat = ac1.create_chat_by_contact(c2) assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL - wait_msgs_changed(ac1, 0, 0) # why no chat id? + wait_msgs_changed(ac1, [(0, 0)]) # why no chat id? lp.sec("create a message with a file in creation") orig = data.get_path("d.png") @@ -78,19 +76,16 @@ class TestOnlineInCreation: fp.write("preparing") prepared_original = chat.prepare_message_file(path) assert prepared_original.is_out_preparing() - wait_msgs_changed(ac1, chat.id, prepared_original.id) + wait_msgs_changed(ac1, [(chat.id, prepared_original.id)]) lp.sec("forward the message while still in creation") chat2 = ac1.create_group_chat("newgroup") chat2.add_contact(c2) - wait_msgs_changed(ac1, 0, 0) # why not chat id? + wait_msgs_changed(ac1, [(0, 0)]) # why not chat id? ac1.forward_messages([prepared_original], chat2) # XXX there might be two EVENT_MSGS_CHANGED and only one of them # is the one caused by forwarding - forwarded_id = wait_msgs_changed(ac1, chat2.id) - if forwarded_id == 0: - forwarded_id = wait_msgs_changed(ac1, chat2.id) - assert forwarded_id + _, forwarded_id = wait_msgs_changed(ac1, [(chat2.id, None)]) forwarded_msg = ac1.get_message_by_id(forwarded_id) assert forwarded_msg.is_out_preparing() @@ -101,16 +96,10 @@ class TestOnlineInCreation: assert prepared_original.is_out_pending() or prepared_original.is_out_delivered() lp.sec("check that both forwarded and original message are proper.") - forwarded, original = False, False - for chat_id, msg_id in get_msgs_changed(ac1): - if chat_id == chat2.id and msg_id == forwarded_id: - forwarded = True - fwd_msg = ac1.get_message_by_id(forwarded_id) - assert fwd_msg.is_out_pending() or fwd_msg.is_out_delivered() - elif chat_id == chat.id and msg_id == prepared_original.id: - original = True - if forwarded and original: - break + wait_msgs_changed(ac1, [(chat2.id, forwarded_id), (chat.id, prepared_original.id)]) + + fwd_msg = ac1.get_message_by_id(forwarded_id) + assert fwd_msg.is_out_pending() or fwd_msg.is_out_delivered() lp.sec("wait for both messages to be delivered to SMTP") wait_msg_delivered(ac1, [ From 641955a1ec374c597ed2801c366360981e54bef0 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Fri, 22 May 2020 13:06:13 +0200 Subject: [PATCH 078/118] try fix inbox ordering issue --- python/tests/test_account.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/python/tests/test_account.py b/python/tests/test_account.py index 9ca525f59..a553d79d9 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -565,17 +565,15 @@ class TestOnlineAccount: ac1.shutdown() ac2.shutdown() - def xtest_configure_canceled(self, acfactory): + def test_configure_canceled(self, acfactory): ac1 = acfactory.get_online_configuring_account() ac1._configtracker.wait_progress() ac1.stop_ongoing() try: ac1.wait_configure_finish() - except ac1._configtracker.ConfigureFailed: + except Exception: pass - ac1.shutdown() - def test_export_import_self_keys(self, acfactory, tmpdir): ac1, ac2 = acfactory.get_two_online_accounts() @@ -830,9 +828,6 @@ class TestOnlineAccount: assert msg_in.text == "message2" assert msg_in.is_forwarded() - ac1.shutdown() - ac2.shutdown() - def test_send_self_message_and_empty_folder(self, acfactory, lp): ac1 = acfactory.get_one_online_account(mvbox=True, move=True) lp.sec("ac1: create self chat") @@ -842,10 +837,9 @@ class TestOnlineAccount: ac1.empty_server_folders(inbox=True, mvbox=True) ev1 = ac1._evtracker.get_matching("DC_EVENT_IMAP_FOLDER_EMPTIED") ev2 = ac1._evtracker.get_matching("DC_EVENT_IMAP_FOLDER_EMPTIED") - boxes = sorted([ev1.data2, ev2.data2]) - assert boxes == ["DeltaChat", "INBOX"] - - ac1.shutdown() + boxes = [ev1.data2, ev2.data2] + boxes.remove("INBOX") + assert len(boxes) == 1 and boxes[0].endswith("DeltaChat") def test_send_and_receive_message_markseen(self, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() From 371a7552f5406dcb92cb7b9bdef925df77a0af1d Mon Sep 17 00:00:00 2001 From: holger krekel Date: Fri, 22 May 2020 13:06:20 +0200 Subject: [PATCH 079/118] fix superflous shutdowns -- those are called automatically after the test function ends --- python/tests/test_account.py | 82 ------------------------------------ 1 file changed, 82 deletions(-) diff --git a/python/tests/test_account.py b/python/tests/test_account.py index a553d79d9..6315d136e 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -562,9 +562,6 @@ class TestOnlineAccount: assert msg3_in.text == "message3" assert msg3_in.is_encrypted() - ac1.shutdown() - ac2.shutdown() - def test_configure_canceled(self, acfactory): ac1 = acfactory.get_online_configuring_account() ac1._configtracker.wait_progress() @@ -585,9 +582,6 @@ class TestOnlineAccount: ac1._evtracker.consume_events() ac2.import_self_keys(dir.strpath) - ac1.shutdown() - ac2.shutdown() - def test_one_account_send_bcc_setting(self, acfactory, lp): ac1 = acfactory.get_online_configuring_account() ac2 = acfactory.get_online_configuring_account() @@ -641,10 +635,6 @@ class TestOnlineAccount: ev_msg = ac1_clone._evtracker.wait_next_messages_changed() assert ev_msg.text == msg_out.text - ac1.shutdown() - ac2.shutdown() - ac1_clone.shutdown() - def test_send_file_twice_unicode_filename_mangling(self, tmpdir, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() chat = self.get_chat(ac1, ac2) @@ -677,9 +667,6 @@ class TestOnlineAccount: assert msg2.filename.endswith("html.zip") assert msg.filename != msg2.filename - ac1.shutdown() - ac2.shutdown() - def test_send_file_html_attachment(self, tmpdir, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() chat = self.get_chat(ac1, ac2) @@ -704,9 +691,6 @@ class TestOnlineAccount: assert open(msg.filename).read() == content assert msg.filename.endswith(basename) - ac1.shutdown() - ac2.shutdown() - def test_mvbox_sentbox_threads(self, acfactory, lp): lp.sec("ac1: start with mvbox thread") ac1 = acfactory.get_online_configuring_account(mvbox=True, move=True, sentbox=True) @@ -729,9 +713,6 @@ class TestOnlineAccount: assert ev.data2 > const.DC_CHAT_ID_LAST_SPECIAL lp.sec("test finished") - ac1.shutdown() - ac2.shutdown() - def test_move_works(self, acfactory): ac1 = acfactory.get_online_configuring_account() ac2 = acfactory.get_online_configuring_account(mvbox=True, move=True) @@ -745,9 +726,6 @@ class TestOnlineAccount: assert ev.data2 > const.DC_CHAT_ID_LAST_SPECIAL ac2._evtracker.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED") - ac1.shutdown() - ac2.shutdown() - def test_move_works_on_self_sent(self, acfactory): ac1 = acfactory.get_online_configuring_account(mvbox=True, move=True) ac1.set_config("bcc_self", "1") @@ -765,9 +743,6 @@ class TestOnlineAccount: ac1._evtracker.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED") ac1._evtracker.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED") - ac1.shutdown() - ac2.shutdown() - def test_forward_messages(self, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() chat = self.get_chat(ac1, ac2) @@ -801,9 +776,6 @@ class TestOnlineAccount: ac2.delete_messages(messages) assert not chat3.get_messages() - ac1.shutdown() - ac2.shutdown() - def test_forward_own_message(self, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() chat = self.get_chat(ac1, ac2, both_created=True) @@ -910,9 +882,6 @@ class TestOnlineAccount: except queue.Empty: pass # mark_seen_messages() has generated events before it returns - ac1.shutdown() - ac2.shutdown() - def test_mdn_asymetric(self, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts(move=True) @@ -948,9 +917,6 @@ class TestOnlineAccount: # MDN is received even though MDNs are already disabled assert msg_out.is_out_mdn_received() - ac1.shutdown() - ac2.shutdown() - def test_send_and_receive_will_encrypt_decrypt(self, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() @@ -1000,9 +966,6 @@ class TestOnlineAccount: ev = ac1._evtracker.get_matching("DC_EVENT_SMTP_MESSAGE_SENT") assert not msg.is_encrypted() - ac1.shutdown() - ac2.shutdown() - def test_send_first_message_as_long_unicode_with_cr(self, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() ac2.set_config("save_mime_headers", "1") @@ -1029,9 +992,6 @@ class TestOnlineAccount: assert msg_in.text == text2 assert ac1.get_config("addr") in msg_in.chat.get_name() - ac1.shutdown() - ac2.shutdown() - def test_reply_encrypted(self, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() @@ -1085,9 +1045,6 @@ class TestOnlineAccount: assert msg_in.text == "message2 -- should be encrypted" assert msg_in.is_encrypted() - ac1.shutdown() - ac2.shutdown() - def test_saved_mime_on_received_message(self, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() @@ -1107,9 +1064,6 @@ class TestOnlineAccount: assert mime.get_all("From") assert mime.get_all("Received") - ac1.shutdown() - ac2.shutdown() - def test_send_and_receive_image(self, acfactory, lp, data): ac1, ac2 = acfactory.get_two_online_accounts() chat = self.get_chat(ac1, ac2) @@ -1158,9 +1112,6 @@ class TestOnlineAccount: m = message_queue.get() assert m == msg_in - ac1.shutdown() - ac2.shutdown() - def test_import_export_online_all(self, acfactory, tmpdir, lp): ac1 = acfactory.get_one_online_account() @@ -1204,9 +1155,6 @@ class TestOnlineAccount: assert path2 != path assert ac2.get_latest_backupfile(backupdir.strpath) == path2 - ac1.shutdown() - ac2.shutdown() - def test_ac_setup_message(self, acfactory, lp): # note that the receiving account needs to be configured and running # before ther setup message is send. DC does not read old messages @@ -1235,9 +1183,6 @@ class TestOnlineAccount: msg.continue_key_transfer(setup_code) assert ac1.get_info()["fingerprint"] == ac2.get_info()["fingerprint"] - ac1.shutdown() - ac2.shutdown() - def test_ac_setup_message_twice(self, acfactory, lp): ac1 = acfactory.get_online_configuring_account() ac2 = acfactory.clone_online_account(ac1) @@ -1263,9 +1208,6 @@ class TestOnlineAccount: msg.continue_key_transfer(setup_code2) assert ac1.get_info()["fingerprint"] == ac2.get_info()["fingerprint"] - ac1.shutdown() - ac2.shutdown() - def test_qr_setup_contact(self, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() lp.sec("ac1: create QR code and let ac2 scan it, starting the securejoin") @@ -1276,9 +1218,6 @@ class TestOnlineAccount: assert ch.id >= 10 ac1._evtracker.wait_securejoin_inviter_progress(1000) - ac1.shutdown() - ac2.shutdown() - def test_qr_join_chat(self, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() lp.sec("ac1: create QR code and let ac2 scan it, starting the securejoin") @@ -1293,9 +1232,6 @@ class TestOnlineAccount: ac2._evtracker.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED") ac1._evtracker.wait_securejoin_inviter_progress(1000) - ac1.shutdown() - ac2.shutdown() - def test_qr_verified_group_and_chatting(self, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() lp.sec("ac1: create verified-group QR, ac2 scans and joins") @@ -1328,9 +1264,6 @@ class TestOnlineAccount: assert msg.text == "world" assert msg.is_encrypted() - ac1.shutdown() - ac2.shutdown() - def test_set_get_contact_avatar(self, acfactory, data, lp): lp.sec("configuring ac1 and ac2") ac1, ac2 = acfactory.get_two_online_accounts() @@ -1378,9 +1311,6 @@ class TestOnlineAccount: msg3 = ac2._evtracker.wait_next_incoming_message() assert msg3.get_sender_contact().get_profile_image() is None - ac1.shutdown() - ac2.shutdown() - def test_add_remove_member_remote_events(self, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() ac1_addr = ac1.get_config("addr") @@ -1455,9 +1385,6 @@ class TestOnlineAccount: assert ev.action == "removed" assert ev.message.get_sender_contact().addr == ac1_addr - ac1.shutdown() - ac2.shutdown() - def test_set_get_group_image(self, acfactory, data, lp): ac1, ac2 = acfactory.get_two_online_accounts() @@ -1511,9 +1438,6 @@ class TestOnlineAccount: assert chat1b.get_profile_image() is None assert chat.get_profile_image() is None - ac1.shutdown() - ac2.shutdown() - def test_accept_sender_contact(self, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() ch = ac1.create_chat_by_contact(ac1.create_contact(ac2.get_config("addr"))) @@ -1523,9 +1447,6 @@ class TestOnlineAccount: msg.accept_sender_contact() assert not msg.chat.is_deaddrop() - ac1.shutdown() - ac2.shutdown() - def test_send_receive_locations(self, acfactory, lp): now = datetime.utcnow() ac1, ac2 = acfactory.get_two_online_accounts() @@ -1574,9 +1495,6 @@ class TestOnlineAccount: locations3 = chat2.get_locations(contact=contact) assert not locations3 - ac1.shutdown() - ac2.shutdown() - class TestGroupStressTests: def test_group_many_members_add_leave_remove(self, acfactory, lp): From 12e66f5a969f6e591926d9b2fa28f058151a87f1 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Fri, 22 May 2020 13:28:49 +0200 Subject: [PATCH 080/118] switch to stable toolchain by default --- Cargo.toml | 2 +- deltachat-ffi/Cargo.toml | 2 +- rust-toolchain | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 802a9daf6..e209a069b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,7 +90,7 @@ required-features = ["repl"] [features] -default = ["nightly"] +default = [] internals = [] repl = ["internals", "rustyline", "log", "pretty_env_logger", "ansi_term"] vendored = ["async-native-tls/vendored", "async-smtp/native-tls-vendored"] diff --git a/deltachat-ffi/Cargo.toml b/deltachat-ffi/Cargo.toml index 22af8af1d..3bd11869f 100644 --- a/deltachat-ffi/Cargo.toml +++ b/deltachat-ffi/Cargo.toml @@ -25,7 +25,7 @@ anyhow = "1.0.28" thiserror = "1.0.14" [features] -default = ["vendored", "nightly"] +default = ["vendored"] vendored = ["deltachat/vendored"] nightly = ["deltachat/nightly"] diff --git a/rust-toolchain b/rust-toolchain index 10a652fd3..3987c4729 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2020-03-19 +1.43.1 From 23ceda5ad9cfdd9cc233361c162aa489826c0f9a Mon Sep 17 00:00:00 2001 From: holger krekel Date: Fri, 22 May 2020 14:58:17 +0200 Subject: [PATCH 081/118] add notes from little a/v we just did --- ASYNC-API-TODO.txt | 55 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 ASYNC-API-TODO.txt diff --git a/ASYNC-API-TODO.txt b/ASYNC-API-TODO.txt new file mode 100644 index 000000000..27ac18efa --- /dev/null +++ b/ASYNC-API-TODO.txt @@ -0,0 +1,55 @@ + +Delta Chat ASYNC (friedel, bjoern, floris, friedel) + +- smtp fake-idle/load jobs gerade noch alle fuenf sekunden , sollte alle zehn minuten (oder gar nicht) + +APIs: + dc_context_new # opens the database + dc_open # FFI only + -> drop it and move parameters to dc_context_new() + + dc_configure # note: dc_start_jobs() is NOT allowed to run concurrently + dc_imex NEVER goes through the job system + dc_imex import_backup needs to ensure dc_stop_jobs() + dc_jobs_are_running() # tell if we are in start/stop-jobs state + + dc_context_run # start async scheduler + -> dc_start_jobs + dc_context_shutdown # Stop async scheduler (after return no async-task runs) + -> dc_stop_jobs + dc_close # FFI only + -> can be dropped + dc_context_unref + + for ios share-extension: + Int dc_direct_send() -> try send out without going through jobs system, but queue a job in db if it needs to be retried on failure + 0: message was sent + 1: message failed to go out, is queued as a job to be retried later + 2: message permanently failed? + +EVENT handling: + start a callback thread and call get_next_event() which is BLOCKING + it's fine to start this callback thread later, it will see all events. + Note that the core infinitely fills the internal queue if you never drain it. + + FFI-get_next_event() returns NULL if the context is unrefed already? + + sidenote: how python's callback thread does it currently: + CB-thread runs this while loop: + while not QUITFLAG: + ev = context.get_next_event( ) + ... + So in order to shutdown properly one has to set QUITFLAG + before calling dc_stop_jobs() and dc_context_unref + + event API: + get_data1_int + get_data2_int + get_data3_str + + +- userdata likely only used for the callbacks, likely can be dropped, needs verification + + +- iOS needs for the share app to call "try_send_smtp" wihtout a full dc_context_run and without going + From 229606fcc549155d86517125871b799bf9e20ebf Mon Sep 17 00:00:00 2001 From: holger krekel Date: Fri, 22 May 2020 15:12:55 +0200 Subject: [PATCH 082/118] fix last failing test -- make account.shutdown() robust against getting called from event thread --- python/examples/test_examples.py | 11 +++++++++-- python/src/deltachat/account.py | 1 + python/src/deltachat/events.py | 4 ++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/python/examples/test_examples.py b/python/examples/test_examples.py index da32b127d..c9c38c26d 100644 --- a/python/examples/test_examples.py +++ b/python/examples/test_examples.py @@ -3,7 +3,7 @@ import pytest import py import echo_and_quit import group_tracking -from deltachat.eventlogger import FFIEventLogger +from deltachat.events import FFIEventLogger @pytest.fixture(scope='session') @@ -17,16 +17,23 @@ def datadir(): pytest.skip('test-data directory not found') -def test_echo_quit_plugin(acfactory): +def test_echo_quit_plugin(acfactory, lp): + lp.sec("creating one echo_and_quit bot") botproc = acfactory.run_bot_process(echo_and_quit) + lp.sec("creating a temp account to contact the bot") ac1 = acfactory.get_one_online_account() + + lp.sec("sending a message to the bot") bot_contact = ac1.create_contact(botproc.addr) ch1 = ac1.create_chat_by_contact(bot_contact) ch1.send_text("hello") + + lp.sec("waiting for the bot-reply to arrive") reply = ac1._evtracker.wait_next_incoming_message() assert "hello" in reply.text assert reply.chat == ch1 + lp.sec("send quit sequence") ch1.send_text("/quit") botproc.wait() diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index 8b8f01741..54761fada 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -636,6 +636,7 @@ class Account(object): self._shutdown_event.set() hook = hookspec.Global._get_plugin_manager().hook hook.dc_account_after_shutdown(account=self, dc_context=dc_context) + self.log("shutdown finished") def _destroy_dc_context(dc_context, dc_context_unref=lib.dc_context_unref): diff --git a/python/src/deltachat/events.py b/python/src/deltachat/events.py index bd23e3359..95007ca7c 100644 --- a/python/src/deltachat/events.py +++ b/python/src/deltachat/events.py @@ -147,6 +147,10 @@ class EventThread(threading.Thread): self._thread_quitflag = True if wait: + if self == threading.current_thread(): + # we are in the callback thread and thus cannot + # wait for the thread-loop to finish. + return self.join() def run(self): From 916935b8d04b8b72964aaf7fdf60e86c7afdd947 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Fri, 22 May 2020 13:17:30 +0200 Subject: [PATCH 083/118] ffi: refactor event handling --- deltachat-ffi/deltachat.h | 7 +- deltachat-ffi/src/lib.rs | 179 +++++++++++++++++++++----------------- 2 files changed, 103 insertions(+), 83 deletions(-) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 896f5dfc1..791d1600f 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -195,9 +195,10 @@ typedef struct _dc_event dc_event_t; */ dc_event_t* dc_get_next_event(dc_context_t* context); -int dc_event_get_id (dc_event_t* event); -uintptr_t dc_event_get_data1(dc_event_t* event); -uintptr_t dc_event_get_data2(dc_event_t* event); +int dc_event_get_id (dc_event_t* event); +int dc_event_get_data1(dc_event_t* event); +int dc_event_get_data2(dc_event_t* event); +char* dc_event_get_data_string(dc_event_t* event); /** * TODO: document diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index e4cf12633..094b40883 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -15,7 +15,6 @@ extern crate serde_json; use std::collections::BTreeMap; use std::convert::TryInto; -use std::ffi::CString; use std::fmt::Write; use std::ptr; use std::str::FromStr; @@ -501,14 +500,8 @@ pub unsafe extern "C" fn dc_is_running(context: *mut dc_context_t) -> libc::c_in with_inner_async!(ffi_context, ctx, { ctx.is_running() }).unwrap_or_default() as libc::c_int } -pub struct EventWrapper { - pub event_id: libc::c_int, - pub data1: uintptr_t, - pub data2: uintptr_t, -} - #[no_mangle] -pub type dc_event_t = EventWrapper; +pub type dc_event_t = Event; #[no_mangle] pub unsafe extern "C" fn dc_event_unref(a: *mut dc_event_t) { @@ -528,50 +521,99 @@ pub unsafe extern "C" fn dc_event_get_id(event: *mut dc_event_t) -> libc::c_int } let event = &*event; - event.event_id + event.as_id() } #[no_mangle] -pub unsafe extern "C" fn dc_event_get_data1(event: *mut dc_event_t) -> uintptr_t { +pub unsafe extern "C" fn dc_event_get_data1(event: *mut dc_event_t) -> libc::c_int { if event.is_null() { eprintln!("ignoring careless call to dc_event_get_data1()"); return 0; } let event = &*event; - event.data1 + match event { + Event::Info(_) + | Event::SmtpConnected(_) + | Event::ImapConnected(_) + | Event::SmtpMessageSent(_) + | Event::ImapMessageDeleted(_) + | Event::ImapMessageMoved(_) + | Event::ImapFolderEmptied(_) + | Event::NewBlobFile(_) + | Event::DeletedBlobFile(_) + | Event::Warning(_) + | Event::Error(_) + | Event::ErrorNetwork(_) + | Event::ErrorSelfNotInGroup(_) => 0, + Event::MsgsChanged { chat_id, .. } + | Event::IncomingMsg { chat_id, .. } + | Event::MsgDelivered { chat_id, .. } + | Event::MsgFailed { chat_id, .. } + | Event::MsgRead { chat_id, .. } + | Event::ChatModified(chat_id) => chat_id.to_u32() as libc::c_int, + Event::ContactsChanged(id) | Event::LocationChanged(id) => { + let id = id.unwrap_or_default(); + id as libc::c_int + } + Event::ConfigureProgress(progress) | Event::ImexProgress(progress) => { + *progress as libc::c_int + } + Event::ImexFileWritten(_) => 0, + Event::SecurejoinInviterProgress { contact_id, .. } + | Event::SecurejoinJoinerProgress { contact_id, .. } => *contact_id as libc::c_int, + } } #[no_mangle] -pub unsafe extern "C" fn dc_event_get_data2(event: *mut dc_event_t) -> uintptr_t { +pub unsafe extern "C" fn dc_event_get_data2(event: *mut dc_event_t) -> libc::c_int { if event.is_null() { eprintln!("ignoring careless call to dc_event_get_data2()"); return 0; } let event = &*event; - event.data2 + + match event { + Event::Info(_) + | Event::SmtpConnected(_) + | Event::ImapConnected(_) + | Event::SmtpMessageSent(_) + | Event::ImapMessageDeleted(_) + | Event::ImapMessageMoved(_) + | Event::ImapFolderEmptied(_) + | Event::NewBlobFile(_) + | Event::DeletedBlobFile(_) + | Event::Warning(_) + | Event::Error(_) + | Event::ErrorNetwork(_) + | Event::ErrorSelfNotInGroup(_) + | Event::ContactsChanged(_) + | Event::LocationChanged(_) + | Event::ConfigureProgress(_) + | Event::ImexProgress(_) + | Event::ImexFileWritten(_) + | Event::ChatModified(_) => 0, + Event::MsgsChanged { msg_id, .. } + | Event::IncomingMsg { msg_id, .. } + | Event::MsgDelivered { msg_id, .. } + | Event::MsgFailed { msg_id, .. } + | Event::MsgRead { msg_id, .. } => msg_id.to_u32() as libc::c_int, + Event::SecurejoinInviterProgress { progress, .. } + | Event::SecurejoinJoinerProgress { progress, .. } => *progress as libc::c_int, + } } #[no_mangle] -pub unsafe extern "C" fn dc_get_next_event(context: *mut dc_context_t) -> *mut dc_event_t { - if context.is_null() { +pub unsafe extern "C" fn dc_event_get_data_string(event: *mut dc_event_t) -> *mut libc::c_char { + if event.is_null() { + eprintln!("ignoring careless call to dc_event_get_data_string()"); return ptr::null_mut(); } - let ffi_context = &*context; - ffi_context - .with_inner(|ctx| match ctx.get_next_event() { - Ok(ev) => translate_event(ev), - Err(_) => ptr::null_mut(), - }) - .unwrap_or_else(|_| ptr::null_mut()) -} + let event = &*event; -/// Translates the callback from the rust style to the C-style version. -unsafe fn translate_event(event: Event) -> *mut dc_event_t { - let event_id = event.as_id(); - let wrapper = match event { + match event { Event::Info(msg) | Event::SmtpConnected(msg) | Event::ImapConnected(msg) @@ -585,64 +627,41 @@ unsafe fn translate_event(event: Event) -> *mut dc_event_t { | Event::Error(msg) | Event::ErrorNetwork(msg) | Event::ErrorSelfNotInGroup(msg) => { - let data2 = CString::new(msg).unwrap_or_default(); - - EventWrapper { - event_id, - data1: 0, - data2: data2.into_raw() as uintptr_t, - } + let data2 = msg.to_c_string().unwrap_or_default(); + data2.into_raw() } - Event::MsgsChanged { chat_id, msg_id } - | Event::IncomingMsg { chat_id, msg_id } - | Event::MsgDelivered { chat_id, msg_id } - | Event::MsgFailed { chat_id, msg_id } - | Event::MsgRead { chat_id, msg_id } => EventWrapper { - event_id, - data1: chat_id.to_u32() as uintptr_t, - data2: msg_id.to_u32() as uintptr_t, - }, - Event::ChatModified(chat_id) => EventWrapper { - event_id, - data1: chat_id.to_u32() as uintptr_t, - data2: 0, - }, - Event::ContactsChanged(id) | Event::LocationChanged(id) => { - let id = id.unwrap_or_default(); - EventWrapper { - event_id, - data1: id as uintptr_t, - data2: 0, - } - } - Event::ConfigureProgress(progress) | Event::ImexProgress(progress) => EventWrapper { - event_id, - data1: progress as uintptr_t, - data2: 0, - }, + Event::MsgsChanged { .. } + | Event::IncomingMsg { .. } + | Event::MsgDelivered { .. } + | Event::MsgFailed { .. } + | Event::MsgRead { .. } + | Event::ChatModified(_) + | Event::ContactsChanged(_) + | Event::LocationChanged(_) + | Event::ConfigureProgress(_) + | Event::ImexProgress(_) + | Event::SecurejoinInviterProgress { .. } + | Event::SecurejoinJoinerProgress { .. } => ptr::null_mut(), Event::ImexFileWritten(file) => { let data1 = file.to_c_string().unwrap_or_default(); - EventWrapper { - event_id, - data1: data1.into_raw() as uintptr_t, - data2: 0, - } + data1.into_raw() } - Event::SecurejoinInviterProgress { - contact_id, - progress, - } - | Event::SecurejoinJoinerProgress { - contact_id, - progress, - } => EventWrapper { - event_id, - data1: contact_id as uintptr_t, - data2: progress as uintptr_t, - }, - }; + } +} - Box::into_raw(Box::new(wrapper)) +#[no_mangle] +pub unsafe extern "C" fn dc_get_next_event(context: *mut dc_context_t) -> *mut dc_event_t { + if context.is_null() { + return ptr::null_mut(); + } + let ffi_context = &*context; + + ffi_context + .with_inner(|ctx| match ctx.get_next_event() { + Ok(ev) => Box::into_raw(Box::new(ev)), + Err(_) => ptr::null_mut(), + }) + .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] From 3947e90b36f2b9c3090c74a6fd6f35b47feda9ae Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Fri, 22 May 2020 13:30:59 +0200 Subject: [PATCH 084/118] update names --- deltachat-ffi/deltachat.h | 6 +++--- deltachat-ffi/src/lib.rs | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 791d1600f..cd29a6665 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -196,9 +196,9 @@ typedef struct _dc_event dc_event_t; dc_event_t* dc_get_next_event(dc_context_t* context); int dc_event_get_id (dc_event_t* event); -int dc_event_get_data1(dc_event_t* event); -int dc_event_get_data2(dc_event_t* event); -char* dc_event_get_data_string(dc_event_t* event); +int dc_event_get_data1_int(dc_event_t* event); +int dc_event_get_data2_int(dc_event_t* event); +char* dc_event_get_data3_str(dc_event_t* event); /** * TODO: document diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 094b40883..122076310 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -525,9 +525,9 @@ pub unsafe extern "C" fn dc_event_get_id(event: *mut dc_event_t) -> libc::c_int } #[no_mangle] -pub unsafe extern "C" fn dc_event_get_data1(event: *mut dc_event_t) -> libc::c_int { +pub unsafe extern "C" fn dc_event_get_data1_int(event: *mut dc_event_t) -> libc::c_int { if event.is_null() { - eprintln!("ignoring careless call to dc_event_get_data1()"); + eprintln!("ignoring careless call to dc_event_get_data1_int()"); return 0; } @@ -566,9 +566,9 @@ pub unsafe extern "C" fn dc_event_get_data1(event: *mut dc_event_t) -> libc::c_i } #[no_mangle] -pub unsafe extern "C" fn dc_event_get_data2(event: *mut dc_event_t) -> libc::c_int { +pub unsafe extern "C" fn dc_event_get_data2_int(event: *mut dc_event_t) -> libc::c_int { if event.is_null() { - eprintln!("ignoring careless call to dc_event_get_data2()"); + eprintln!("ignoring careless call to dc_event_get_data2_int()"); return 0; } @@ -605,9 +605,9 @@ pub unsafe extern "C" fn dc_event_get_data2(event: *mut dc_event_t) -> libc::c_i } #[no_mangle] -pub unsafe extern "C" fn dc_event_get_data_string(event: *mut dc_event_t) -> *mut libc::c_char { +pub unsafe extern "C" fn dc_event_get_data3_str(event: *mut dc_event_t) -> *mut libc::c_char { if event.is_null() { - eprintln!("ignoring careless call to dc_event_get_data_string()"); + eprintln!("ignoring careless call to dc_event_get_data3_str()"); return ptr::null_mut(); } From c43285f6ac79258e329cd576bde90f2345c9d6e7 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Fri, 22 May 2020 16:18:54 +0200 Subject: [PATCH 085/118] fix python data2/string handling and reduce extra code needed --- deltachat-ffi/deltachat.h | 4 ++-- deltachat-ffi/src/lib.rs | 4 ++-- python/install_python_bindings.py | 1 + python/src/deltachat/_build.py | 20 +++----------------- python/src/deltachat/events.py | 20 ++++++-------------- python/src/deltachat/tracker.py | 2 +- python/tests/test_lowlevel.py | 16 +++++++++------- 7 files changed, 24 insertions(+), 43 deletions(-) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index cd29a6665..91d03dafb 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -4202,8 +4202,8 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot); #define DC_ERROR_SEE_STRING 0 // not used anymore #define DC_ERROR_SELF_NOT_IN_GROUP 1 // not used anymore #define DC_STR_SELFNOTINGRP 21 // not used anymore -#define DC_EVENT_DATA1_IS_STRING(e) ((e)==DC_EVENT_IMEX_FILE_WRITTEN || (e)==DC_EVENT_FILE_COPIED) -#define DC_EVENT_DATA2_IS_STRING(e) ((e)>=100 && (e)<=499) +#define DC_EVENT_DATA1_IS_STRING(e) 0 // not used anymore +#define DC_EVENT_DATA2_IS_STRING(e) ((e)==DC_EVENT_IMEX_FILE_WRITTEN || ((e)>=100 && (e)<=499)) #define DC_EVENT_RETURNS_INT(e) ((e)==DC_EVENT_IS_OFFLINE) // not used anymore #define DC_EVENT_RETURNS_STRING(e) ((e)==DC_EVENT_GET_STRING) // not used anymore #define dc_archive_chat(a,b,c) dc_set_chat_visibility((a), (b), (c)? 1 : 0) // not used anymore diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 122076310..d87935f7b 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -643,8 +643,8 @@ pub unsafe extern "C" fn dc_event_get_data3_str(event: *mut dc_event_t) -> *mut | Event::SecurejoinInviterProgress { .. } | Event::SecurejoinJoinerProgress { .. } => ptr::null_mut(), Event::ImexFileWritten(file) => { - let data1 = file.to_c_string().unwrap_or_default(); - data1.into_raw() + let data2 = file.to_c_string().unwrap_or_default(); + data2.into_raw() } } } diff --git a/python/install_python_bindings.py b/python/install_python_bindings.py index 106d7fdbe..bc4fb4cf7 100755 --- a/python/install_python_bindings.py +++ b/python/install_python_bindings.py @@ -19,6 +19,7 @@ if __name__ == "__main__": cmd = ["cargo", "build", "-p", "deltachat_ffi"] if target == 'release': cmd.append("--release") + print("running:", " ".join(cmd)) subprocess.check_call(cmd) subprocess.check_call("rm -rf build/ src/deltachat/*.so" , shell=True) diff --git a/python/src/deltachat/_build.py b/python/src/deltachat/_build.py index e92d9e174..25936755f 100644 --- a/python/src/deltachat/_build.py +++ b/python/src/deltachat/_build.py @@ -45,22 +45,9 @@ def ffibuilder(): 'deltachat.capi', """ #include - const char * dupstring_helper(const char* string) + int dc_event_has_string_data(int e) { - return strdup(string); - } - int dc_get_event_signature_types(int e) - { - int result = 0; - if (DC_EVENT_DATA1_IS_STRING(e)) - result |= 1; - if (DC_EVENT_DATA2_IS_STRING(e)) - result |= 2; - if (DC_EVENT_RETURNS_STRING(e)) - result |= 4; - if (DC_EVENT_RETURNS_INT(e)) - result |= 8; - return result; + return DC_EVENT_DATA2_IS_STRING(e); } """, include_dirs=incs, @@ -71,8 +58,7 @@ def ffibuilder(): builder.cdef(""" typedef int... time_t; void free(void *ptr); - extern const char * dupstring_helper(const char* string); - extern int dc_get_event_signature_types(int); + extern int dc_event_has_string_data(int); """) distutils.log.set_verbosity(distutils.log.INFO) cc = distutils.ccompiler.new_compiler(force=True) diff --git a/python/src/deltachat/events.py b/python/src/deltachat/events.py index 95007ca7c..173c6f983 100644 --- a/python/src/deltachat/events.py +++ b/python/src/deltachat/events.py @@ -8,6 +8,7 @@ from .hookspec import account_hookimpl from contextlib import contextmanager from .capi import ffi, lib from .message import map_system_message +from .cutil import from_dc_charpointer class FFIEvent: @@ -164,23 +165,14 @@ class EventThread(threading.Thread): if event == ffi.NULL: break evt = lib.dc_event_get_id(event) - data1 = lib.dc_event_get_data1(event) - data2 = lib.dc_event_get_data2(event) + data1 = lib.dc_event_get_data1_int(event) # the following code relates to the deltachat/_build.py's helper # function which provides us signature info of an event call evt_name = deltachat.get_dc_event_name(evt) - event_sig_types = lib.dc_get_event_signature_types(evt) - if data1 and event_sig_types & 1: - data1 = ffi.string(ffi.gc(ffi.cast('char*', data1), lib.dc_str_unref)).decode("utf8") - if data2 and event_sig_types & 2: - data2 = ffi.string(ffi.gc(ffi.cast('char*', data2), lib.dc_str_unref)).decode("utf8") - try: - if isinstance(data2, bytes): - data2 = data2.decode("utf8") - except UnicodeDecodeError: - # XXX ignoring the decode error is not quite correct but for now - # i don't want to hunt down encoding problems in the c lib - pass + if lib.dc_event_has_string_data(evt): + data2 = from_dc_charpointer(lib.dc_event_get_data3_str(event)) + else: + data2 = lib.dc_event_get_data2_int(event) lib.dc_event_unref(event) ffi_event = FFIEvent(name=evt_name, data1=data1, data2=data2) diff --git a/python/src/deltachat/tracker.py b/python/src/deltachat/tracker.py index a566c5d5d..bc8122b25 100644 --- a/python/src/deltachat/tracker.py +++ b/python/src/deltachat/tracker.py @@ -18,7 +18,7 @@ class ImexTracker: if ffi_event.name == "DC_EVENT_IMEX_PROGRESS": self._imex_events.put(ffi_event.data1) elif ffi_event.name == "DC_EVENT_IMEX_FILE_WRITTEN": - self._imex_events.put(ffi_event.data1) + self._imex_events.put(ffi_event.data2) def wait_finish(self, progress_timeout=60): """ Return list of written files, raise ValueError if ExportFailed. """ diff --git a/python/tests/test_lowlevel.py b/python/tests/test_lowlevel.py index 247f265f9..466b0504a 100644 --- a/python/tests/test_lowlevel.py +++ b/python/tests/test_lowlevel.py @@ -58,13 +58,15 @@ def test_event_defines(): def test_sig(): - sig = capi.lib.dc_get_event_signature_types - assert sig(const.DC_EVENT_INFO) == 2 - assert sig(const.DC_EVENT_WARNING) == 2 - assert sig(const.DC_EVENT_ERROR) == 2 - assert sig(const.DC_EVENT_SMTP_CONNECTED) == 2 - assert sig(const.DC_EVENT_IMAP_CONNECTED) == 2 - assert sig(const.DC_EVENT_SMTP_MESSAGE_SENT) == 2 + sig = capi.lib.dc_event_has_string_data + assert not sig(const.DC_EVENT_MSGS_CHANGED) + assert sig(const.DC_EVENT_INFO) + assert sig(const.DC_EVENT_WARNING) + assert sig(const.DC_EVENT_ERROR) + assert sig(const.DC_EVENT_SMTP_CONNECTED) + assert sig(const.DC_EVENT_IMAP_CONNECTED) + assert sig(const.DC_EVENT_SMTP_MESSAGE_SENT) + assert sig(const.DC_EVENT_IMEX_FILE_WRITTEN) def test_markseen_invalid_message_ids(acfactory): From d0686ada831d77303b34048ea706facc727bb81c Mon Sep 17 00:00:00 2001 From: holger krekel Date: Fri, 22 May 2020 17:33:16 +0200 Subject: [PATCH 086/118] a round of renaming towards dc_io_* methods for start/stop/io scheduling --- ASYNC-API-TODO.txt | 9 ++++---- deltachat-ffi/deltachat.h | 28 +++++++++++++++++++------ deltachat-ffi/src/lib.rs | 6 +++--- python/fail_test.py | 2 +- python/src/deltachat/account.py | 8 +++---- python/x.py | 37 --------------------------------- 6 files changed, 34 insertions(+), 56 deletions(-) delete mode 100644 python/x.py diff --git a/ASYNC-API-TODO.txt b/ASYNC-API-TODO.txt index 27ac18efa..c037db859 100644 --- a/ASYNC-API-TODO.txt +++ b/ASYNC-API-TODO.txt @@ -11,12 +11,11 @@ APIs: dc_configure # note: dc_start_jobs() is NOT allowed to run concurrently dc_imex NEVER goes through the job system dc_imex import_backup needs to ensure dc_stop_jobs() - dc_jobs_are_running() # tell if we are in start/stop-jobs state - dc_context_run # start async scheduler - -> dc_start_jobs - dc_context_shutdown # Stop async scheduler (after return no async-task runs) - -> dc_stop_jobs + dc_io_start # start smtp/imap and job handling subsystems + dc_io_stop # stop smtp/imap and job handling subsystems + dc_io_status # return 1 if smtp/imap/jobs susbystem is running + dc_close # FFI only -> can be dropped dc_context_unref diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 91d03dafb..22420f2f9 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -583,16 +583,32 @@ int dc_is_configured (const dc_context_t* context); /** - * TODO: Document + * Start job and IMAP/SMTP tasks. + * + * @memberof dc_context_t + * @param context The context object as created by dc_context_new(). + * @return None */ -void dc_context_run (dc_context_t* context); - -int dc_is_running (const dc_context_t* context); +void dc_io_start (dc_context_t* context); /** - * TODO: Document + * Check if IO (SMTP/IMAP/Jobs) has been started. + * + * @memberof dc_context_t + * @param context The context object as created by dc_context_new(). + * @return 1=IO is running; + * 0=IO is not running. */ -void dc_context_shutdown(dc_context_t* context); +int dc_io_status(const dc_context_t* context); + +/** + * Stop job and IMAP/SMTP tasks and return when they are finished. + * + * @memberof dc_context_t + * @param context The context object as created by dc_context_new(). + * @return None + */ +void dc_io_stop(dc_context_t* context); /** * This function can be called whenever there is a hint diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index d87935f7b..6cef31224 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -481,7 +481,7 @@ pub unsafe extern "C" fn dc_is_configured(context: *mut dc_context_t) -> libc::c } #[no_mangle] -pub unsafe extern "C" fn dc_context_run(context: *mut dc_context_t) { +pub unsafe extern "C" fn dc_io_start(context: *mut dc_context_t) { if context.is_null() { return; } @@ -491,7 +491,7 @@ pub unsafe extern "C" fn dc_context_run(context: *mut dc_context_t) { } #[no_mangle] -pub unsafe extern "C" fn dc_is_running(context: *mut dc_context_t) -> libc::c_int { +pub unsafe extern "C" fn dc_io_status(context: *mut dc_context_t) -> libc::c_int { if context.is_null() { return 0; } @@ -665,7 +665,7 @@ pub unsafe extern "C" fn dc_get_next_event(context: *mut dc_context_t) -> *mut d } #[no_mangle] -pub unsafe extern "C" fn dc_context_shutdown(context: *mut dc_context_t) { +pub unsafe extern "C" fn dc_io_stop(context: *mut dc_context_t) { if context.is_null() { eprintln!("ignoring careless call to dc_shutdown()"); return; diff --git a/python/fail_test.py b/python/fail_test.py index 5d1535fdf..736471e71 100644 --- a/python/fail_test.py +++ b/python/fail_test.py @@ -4,4 +4,4 @@ from deltachat.capi import ffi, lib if __name__ == "__main__": ctx = capi.lib.dc_context_new(ffi.NULL, ffi.NULL) - lib.dc_context_shutdown(ctx) + lib.dc_io_stop(ctx) diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index 54761fada..14741bf88 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -575,7 +575,7 @@ class Account(object): if not self.is_configured(): self.configure() self.wait_configure_finish() - lib.dc_context_run(self._dc_context) + lib.dc_io_start(self._dc_context) def configure(self): assert not self.is_configured() @@ -596,7 +596,7 @@ class Account(object): del self._configtracker def is_started(self): - return self._event_thread.is_alive() and bool(lib.dc_is_running(self._dc_context)) + return self._event_thread.is_alive() and bool(lib.dc_io_status(self._dc_context)) def wait_shutdown(self): """ wait until shutdown of this account has completed. """ @@ -607,9 +607,9 @@ class Account(object): self.log("stop_ongoing") self.stop_ongoing() - if bool(lib.dc_is_running(self._dc_context)): + if bool(lib.dc_io_status(self._dc_context)): self.log("context_shutdown (stop core scheduler)") - lib.dc_context_shutdown(self._dc_context) + lib.dc_io_stop(self._dc_context) else: self.log("stop_scheduler called on non-running context") diff --git a/python/x.py b/python/x.py deleted file mode 100644 index 3f7a6cdb1..000000000 --- a/python/x.py +++ /dev/null @@ -1,37 +0,0 @@ - -import deltachat -import os -import shutil -from deltachat.capi import lib - -try: - os.remove("/tmp/db") -except: - pass -try: - shutil.rmtree("/tmp/db-blobs") -except: - pass - - -acc = deltachat.Account("/tmp/db", logging=True) -acc.set_config("addr", "tmp.hjfcq@five.chat") -acc.set_config("mail_pw", "aihWNtLuRJgV") -acc.start() # lib.dc_configure + lib.dc_context_run -assert acc.is_configured() -acc.stop_scheduler() - -run = 0 -while 1: - print("****** starting scheduler") - acc.start() - import time ; time.sleep(0.5) - print("******* stopping scheduler") - acc.stop_scheduler() - print("******* waiting", run) - import time ; time.sleep(1.0) - run += 1 - -contact = acc.create_contact("holger@deltachat.de") -chat = acc.create_chat_by_contact(contact) -chat.send_text("hello") From 4b4e6e1732669d41c6386bcda8d7dd32cd86ce07 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Fri, 22 May 2020 18:08:06 +0200 Subject: [PATCH 087/118] use bjoern's naming suggestion --- ASYNC-API-TODO.txt | 6 +++--- deltachat-ffi/deltachat.h | 6 +++--- deltachat-ffi/src/lib.rs | 6 +++--- python/fail_test.py | 2 +- python/src/deltachat/account.py | 10 +++++----- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/ASYNC-API-TODO.txt b/ASYNC-API-TODO.txt index c037db859..4bdf14092 100644 --- a/ASYNC-API-TODO.txt +++ b/ASYNC-API-TODO.txt @@ -12,9 +12,9 @@ APIs: dc_imex NEVER goes through the job system dc_imex import_backup needs to ensure dc_stop_jobs() - dc_io_start # start smtp/imap and job handling subsystems - dc_io_stop # stop smtp/imap and job handling subsystems - dc_io_status # return 1 if smtp/imap/jobs susbystem is running + dc_start_io # start smtp/imap and job handling subsystems + dc_stop_io # stop smtp/imap and job handling subsystems + dc_is_io_running # return 1 if smtp/imap/jobs susbystem is running dc_close # FFI only -> can be dropped diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 22420f2f9..0556a6184 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -589,7 +589,7 @@ int dc_is_configured (const dc_context_t* context); * @param context The context object as created by dc_context_new(). * @return None */ -void dc_io_start (dc_context_t* context); +void dc_start_io (dc_context_t* context); /** * Check if IO (SMTP/IMAP/Jobs) has been started. @@ -599,7 +599,7 @@ void dc_io_start (dc_context_t* context); * @return 1=IO is running; * 0=IO is not running. */ -int dc_io_status(const dc_context_t* context); +int dc_is_io_running(const dc_context_t* context); /** * Stop job and IMAP/SMTP tasks and return when they are finished. @@ -608,7 +608,7 @@ int dc_io_status(const dc_context_t* context); * @param context The context object as created by dc_context_new(). * @return None */ -void dc_io_stop(dc_context_t* context); +void dc_stop_io(dc_context_t* context); /** * This function can be called whenever there is a hint diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 6cef31224..739cdc0ec 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -481,7 +481,7 @@ pub unsafe extern "C" fn dc_is_configured(context: *mut dc_context_t) -> libc::c } #[no_mangle] -pub unsafe extern "C" fn dc_io_start(context: *mut dc_context_t) { +pub unsafe extern "C" fn dc_start_io(context: *mut dc_context_t) { if context.is_null() { return; } @@ -491,7 +491,7 @@ pub unsafe extern "C" fn dc_io_start(context: *mut dc_context_t) { } #[no_mangle] -pub unsafe extern "C" fn dc_io_status(context: *mut dc_context_t) -> libc::c_int { +pub unsafe extern "C" fn dc_is_io_running(context: *mut dc_context_t) -> libc::c_int { if context.is_null() { return 0; } @@ -665,7 +665,7 @@ pub unsafe extern "C" fn dc_get_next_event(context: *mut dc_context_t) -> *mut d } #[no_mangle] -pub unsafe extern "C" fn dc_io_stop(context: *mut dc_context_t) { +pub unsafe extern "C" fn dc_stop_io(context: *mut dc_context_t) { if context.is_null() { eprintln!("ignoring careless call to dc_shutdown()"); return; diff --git a/python/fail_test.py b/python/fail_test.py index 736471e71..04229e660 100644 --- a/python/fail_test.py +++ b/python/fail_test.py @@ -4,4 +4,4 @@ from deltachat.capi import ffi, lib if __name__ == "__main__": ctx = capi.lib.dc_context_new(ffi.NULL, ffi.NULL) - lib.dc_io_stop(ctx) + lib.dc_stop_io(ctx) diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index 14741bf88..fba5b6186 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -460,7 +460,7 @@ class Account(object): If sending out was unsuccessful, a RuntimeError is raised. """ self.check_is_configured() - if not self._event_thread.is_alive() or not self.is_started(): + if not self.is_started(): raise RuntimeError("IO not running, can not send out") res = lib.dc_initiate_key_transfer(self._dc_context) if res == ffi.NULL: @@ -575,7 +575,7 @@ class Account(object): if not self.is_configured(): self.configure() self.wait_configure_finish() - lib.dc_io_start(self._dc_context) + lib.dc_start_io(self._dc_context) def configure(self): assert not self.is_configured() @@ -596,7 +596,7 @@ class Account(object): del self._configtracker def is_started(self): - return self._event_thread.is_alive() and bool(lib.dc_io_status(self._dc_context)) + return self._event_thread.is_alive() and bool(lib.dc_is_io_running(self._dc_context)) def wait_shutdown(self): """ wait until shutdown of this account has completed. """ @@ -607,9 +607,9 @@ class Account(object): self.log("stop_ongoing") self.stop_ongoing() - if bool(lib.dc_io_status(self._dc_context)): + if bool(lib.dc_is_io_running(self._dc_context)): self.log("context_shutdown (stop core scheduler)") - lib.dc_io_stop(self._dc_context) + lib.dc_stop_io(self._dc_context) else: self.log("stop_scheduler called on non-running context") From 014d2946b2ca9c4627551aff12eb745ac7a5f6ed Mon Sep 17 00:00:00 2001 From: Friedel Ziegelmayer Date: Fri, 22 May 2020 21:03:01 +0200 Subject: [PATCH 088/118] feat: use EventEmitter for events --- Cargo.toml | 8 +++-- deltachat-ffi/deltachat.h | 6 +++- deltachat-ffi/src/lib.rs | 39 +++++++++++++++++++----- examples/repl/cmdline.rs | 6 ++-- examples/repl/main.rs | 4 +-- examples/simple.rs | 4 +-- python/src/deltachat/account.py | 17 +++++------ python/src/deltachat/events.py | 33 ++++++++------------ src/blob.rs | 4 +-- src/chat.rs | 40 ++++++++++++------------ src/config.rs | 2 +- src/configure/mod.rs | 2 +- src/contact.rs | 12 ++++---- src/context.rs | 20 ++++++------ src/dc_receive_imf.rs | 12 ++++---- src/dc_tools.rs | 2 +- src/events.rs | 54 +++++++++++++++++++++++++++++++++ src/imex.rs | 14 ++++----- src/job.rs | 2 +- src/location.rs | 8 ++--- src/log.rs | 2 +- src/message.rs | 6 ++-- src/mimeparser.rs | 2 +- src/securejoin.rs | 4 +-- src/smtp/mod.rs | 4 +-- src/smtp/send.rs | 2 +- 26 files changed, 192 insertions(+), 117 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e209a069b..6591225e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,14 +57,15 @@ image = { version = "0.22.4", default-features=false, features = ["gif_codec", " futures = "0.3.4" thiserror = "1.0.14" anyhow = "1.0.28" +async-trait = "0.1.31" +url = "2.1.1" +crossbeam-channel = "0.4.2" pretty_env_logger = { version = "0.3.1", optional = true } log = {version = "0.4.8", optional = true } rustyline = { version = "4.1.0", optional = true } ansi_term = { version = "0.12.1", optional = true } -async-trait = "0.1.31" -crossbeam-channel = "0.4.2" -url = "2.1.1" + [dev-dependencies] tempfile = "3.0" @@ -95,3 +96,4 @@ internals = [] repl = ["internals", "rustyline", "log", "pretty_env_logger", "ansi_term"] vendored = ["async-native-tls/vendored", "async-smtp/native-tls-vendored"] nightly = ["pgp/nightly"] + diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 0556a6184..de84d9699 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -20,6 +20,7 @@ typedef struct _dc_contact dc_contact_t; typedef struct _dc_lot dc_lot_t; typedef struct _dc_provider dc_provider_t; typedef struct _dc_event dc_event_t; + typedef struct _dc_event_emitter dc_event_emitter_t; /** @@ -193,7 +194,10 @@ typedef struct _dc_event dc_event_t; /** * TODO: document */ -dc_event_t* dc_get_next_event(dc_context_t* context); +dc_event_t* dc_get_next_event(dc_event_emitter_t* emitter); + +dc_event_emitter_t* dc_get_event_emitter(dc_context_t* context); +void dc_event_emitter_unref(dc_event_emitter_t* emitter); int dc_event_get_id (dc_event_t* event); int dc_event_get_data1_int(dc_event_t* event); diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 739cdc0ec..17d1aed7f 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -91,7 +91,7 @@ impl ContextWrapper { /// logfile rather than being shown directly to the user. unsafe fn warning(&self, msg: &str) { self.with_inner(|ctx| { - ctx.call_cb(Event::Warning(msg.to_string())); + ctx.emit_event(Event::Warning(msg.to_string())); }) .unwrap(); } @@ -650,20 +650,45 @@ pub unsafe extern "C" fn dc_event_get_data3_str(event: *mut dc_event_t) -> *mut } #[no_mangle] -pub unsafe extern "C" fn dc_get_next_event(context: *mut dc_context_t) -> *mut dc_event_t { +pub type dc_event_emitter_t = EventEmitter; + +#[no_mangle] +pub unsafe extern "C" fn dc_get_event_emitter( + context: *mut dc_context_t, +) -> *mut dc_event_emitter_t { if context.is_null() { + eprintln!("ignoring careless call to dc_get_event_emitter()"); return ptr::null_mut(); } let ffi_context = &*context; - ffi_context - .with_inner(|ctx| match ctx.get_next_event() { - Ok(ev) => Box::into_raw(Box::new(ev)), - Err(_) => ptr::null_mut(), - }) + .with_inner(|ctx| Box::into_raw(Box::new(ctx.get_event_emitter()))) .unwrap_or_else(|_| ptr::null_mut()) } +#[no_mangle] +pub unsafe extern "C" fn dc_event_emitter_unref(emitter: *mut dc_event_emitter_t) { + if emitter.is_null() { + eprintln!("ignoring careless call to dc_event_mitter_unref()"); + return; + } + + Box::from_raw(emitter); +} + +#[no_mangle] +pub unsafe extern "C" fn dc_get_next_event(events: *mut dc_event_emitter_t) -> *mut dc_event_t { + if events.is_null() { + return ptr::null_mut(); + } + let events = &*events; + + events + .recv_sync() + .map(|ev| Box::into_raw(Box::new(ev))) + .unwrap_or_else(|| ptr::null_mut()) +} + #[no_mangle] pub unsafe extern "C" fn dc_stop_io(context: *mut dc_context_t) { if context.is_null() { diff --git a/examples/repl/cmdline.rs b/examples/repl/cmdline.rs index dc4659311..619988837 100644 --- a/examples/repl/cmdline.rs +++ b/examples/repl/cmdline.rs @@ -86,7 +86,7 @@ async fn reset_tables(context: &Context, bits: i32) { println!("(8) Rest but server config reset."); } - context.call_cb(Event::MsgsChanged { + context.emit_event(Event::MsgsChanged { chat_id: ChatId::new(0), msg_id: MsgId::new(0), }); @@ -157,7 +157,7 @@ async fn poke_spec(context: &Context, spec: Option<&str>) -> bool { } println!("Import: {} items read from \"{}\".", read_cnt, &real_spec); if read_cnt > 0 { - context.call_cb(Event::MsgsChanged { + context.emit_event(Event::MsgsChanged { chat_id: ChatId::new(0), msg_id: MsgId::new(0), }); @@ -1030,7 +1030,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu // ensure!(!arg1.is_empty(), "Argument missing."); // let event = arg1.parse()?; // let event = Event::from_u32(event).ok_or(format_err!("Event::from_u32({})", event))?; - // let r = context.call_cb(event, 0 as libc::uintptr_t, 0 as libc::uintptr_t); + // let r = context.emit_event(event, 0 as libc::uintptr_t, 0 as libc::uintptr_t); // println!( // "Sending event {:?}({}), received value {}.", // event, event as usize, r as libc::c_int, diff --git a/examples/repl/main.rs b/examples/repl/main.rs index 04d0fe105..86a2088f6 100644 --- a/examples/repl/main.rs +++ b/examples/repl/main.rs @@ -275,10 +275,10 @@ async fn start(args: Vec) -> Result<(), Error> { } let context = Context::new("CLI".into(), Path::new(&args[1]).to_path_buf()).await?; - let ctx = context.clone(); + let events = context.get_event_emitter(); async_std::task::spawn(async move { loop { - if let Ok(event) = ctx.get_next_event() { + if let Some(event) = events.recv().await { receive_event(event); } } diff --git a/examples/simple.rs b/examples/simple.rs index 2542c89cd..694d901f0 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -38,10 +38,10 @@ async fn main() { let duration = time::Duration::from_millis(4000); println!("info: {:#?}", info); - let ctx1 = ctx.clone(); + let events = ctx.get_event_emitter(); async_std::task::spawn(async move { loop { - if let Ok(event) = ctx1.get_next_event() { + if let Some(event) = events.recv().await { cb(event); } } diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index fba5b6186..3925d3292 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -613,24 +613,23 @@ class Account(object): else: self.log("stop_scheduler called on non-running context") - def shutdown(self, wait=True): + def shutdown(self): """ shutdown and destroy account (stop callback thread, close and remove - underlying dc_context.""" + underlying dc_context).""" dc_context = self._dc_context if dc_context is None: return - if self._event_thread.is_alive(): - self.log("stop threads") - self._event_thread.stop(wait=False) - self.stop_scheduler() self.log("dc_close") + # the dc_close triggers get_next_event to return ffi.NULL + # which in turns makes the event thread finish execution lib.dc_close(dc_context) - self.log("wait threads for real") - if wait: - self._event_thread.stop(wait=wait) + + self.log("wait for event thread to finish") + self._event_thread.wait() + self._dc_context = None atexit.unregister(self.shutdown) self._shutdown_event.set() diff --git a/python/src/deltachat/events.py b/python/src/deltachat/events.py index 173c6f983..10a59fc2c 100644 --- a/python/src/deltachat/events.py +++ b/python/src/deltachat/events.py @@ -134,7 +134,6 @@ class EventThread(threading.Thread): def __init__(self, account): self.account = account self._dc_context = account._dc_context - self._thread_quitflag = False super(EventThread, self).__init__(name="events") self.start() @@ -144,15 +143,12 @@ class EventThread(threading.Thread): yield self.account.log(message + " FINISHED") - def stop(self, wait=False): - self._thread_quitflag = True - - if wait: - if self == threading.current_thread(): - # we are in the callback thread and thus cannot - # wait for the thread-loop to finish. - return - self.join() + def wait(self): + if self == threading.current_thread(): + # we are in the callback thread and thus cannot + # wait for the thread-loop to finish. + return + self.join() def run(self): """ get and run events until shutdown. """ @@ -160,8 +156,12 @@ class EventThread(threading.Thread): self._inner_run() def _inner_run(self): - while lib.dc_is_open(self._dc_context) and not self._thread_quitflag: - event = lib.dc_get_next_event(self._dc_context) + event_emitter = ffi.gc( + lib.dc_get_event_emitter(self._dc_context), + lib.dc_event_emitter_unref, + ) + while 1: + event = lib.dc_get_next_event(event_emitter) if event == ffi.NULL: break evt = lib.dc_event_get_id(event) @@ -180,14 +180,7 @@ class EventThread(threading.Thread): for name, kwargs in self._map_ffi_event(ffi_event): # self.account.log("calling hook name={} kwargs={}".format(name, kwargs)) hook = getattr(self.account._pm.hook, name) - try: - hook(**kwargs) - except Exception: - # don't bother logging this error - # if dc_close() was concurrently called - # (note: core API starts failing after that) - if not self._thread_quitflag: - raise + hook(**kwargs) def _map_ffi_event(self, ffi_event): name = ffi_event.name diff --git a/src/blob.rs b/src/blob.rs index 919c42f9b..bbb5635c9 100644 --- a/src/blob.rs +++ b/src/blob.rs @@ -63,7 +63,7 @@ impl<'a> BlobObject<'a> { blobdir, name: format!("$BLOBDIR/{}", name), }; - context.call_cb(Event::NewBlobFile(blob.as_name().to_string())); + context.emit_event(Event::NewBlobFile(blob.as_name().to_string())); Ok(blob) } @@ -151,7 +151,7 @@ impl<'a> BlobObject<'a> { blobdir: context.get_blobdir(), name: format!("$BLOBDIR/{}", name), }; - context.call_cb(Event::NewBlobFile(blob.as_name().to_string())); + context.emit_event(Event::NewBlobFile(blob.as_name().to_string())); Ok(blob) } diff --git a/src/chat.rs b/src/chat.rs index 4caa095ab..00921bc6c 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -174,7 +174,7 @@ impl ChatId { ) .await?; - context.call_cb(Event::MsgsChanged { + context.emit_event(Event::MsgsChanged { msg_id: MsgId::new(0), chat_id: ChatId::new(0), }); @@ -231,7 +231,7 @@ impl ChatId { .execute("DELETE FROM chats WHERE id=?;", paramsv![self]) .await?; - context.call_cb(Event::MsgsChanged { + context.emit_event(Event::MsgsChanged { msg_id: MsgId::new(0), chat_id: ChatId::new(0), }); @@ -256,7 +256,7 @@ impl ChatId { }; if changed { - context.call_cb(Event::MsgsChanged { + context.emit_event(Event::MsgsChanged { chat_id: self, msg_id: MsgId::new(0), }); @@ -1098,7 +1098,7 @@ pub async fn create_by_msg_id(context: &Context, msg_id: MsgId) -> Result Result< } }; - context.call_cb(Event::MsgsChanged { + context.emit_event(Event::MsgsChanged { chat_id: ChatId::new(0), msg_id: MsgId::new(0), }); @@ -1307,7 +1307,7 @@ pub async fn prepare_msg( msg.state = MessageState::OutPreparing; let msg_id = prepare_msg_common(context, chat_id, msg).await?; - context.call_cb(Event::MsgsChanged { + context.emit_event(Event::MsgsChanged { chat_id: msg.chat_id, msg_id: msg.id, }); @@ -1476,13 +1476,13 @@ async fn send_msg_inner( } job::send_msg(context, msg.id).await?; - context.call_cb(Event::MsgsChanged { + context.emit_event(Event::MsgsChanged { chat_id: msg.chat_id, msg_id: msg.id, }); if msg.param.exists(Param::SetLatitude) { - context.call_cb(Event::LocationChanged(Some(DC_CONTACT_ID_SELF))); + context.emit_event(Event::LocationChanged(Some(DC_CONTACT_ID_SELF))); } Ok(msg.id) @@ -1514,7 +1514,7 @@ pub async fn get_chat_msgs( Err(err) => warn!(context, "Failed to delete expired messages: {}", err), Ok(messages_deleted) => { if messages_deleted { - context.call_cb(Event::MsgsChanged { + context.emit_event(Event::MsgsChanged { msg_id: MsgId::new(0), chat_id: ChatId::new(0), }) @@ -1635,7 +1635,7 @@ pub async fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<(), ) .await?; - context.call_cb(Event::MsgsChanged { + context.emit_event(Event::MsgsChanged { chat_id: ChatId::new(0), msg_id: MsgId::new(0), }); @@ -1667,7 +1667,7 @@ pub async fn marknoticed_all_chats(context: &Context) -> Result<(), Error> { ) .await?; - context.call_cb(Event::MsgsChanged { + context.emit_event(Event::MsgsChanged { msg_id: MsgId::new(0), chat_id: ChatId::new(0), }); @@ -1884,7 +1884,7 @@ pub async fn create_group_chat( chat_id.set_draft_raw(context, &mut draft_msg).await; } - context.call_cb(Event::MsgsChanged { + context.emit_event(Event::MsgsChanged { msg_id: MsgId::new(0), chat_id: ChatId::new(0), }); @@ -2043,7 +2043,7 @@ pub(crate) async fn add_contact_to_chat_ex( msg.param.set_int(Param::Arg2, from_handshake.into()); msg.id = send_msg(context, chat_id, &mut msg).await?; } - context.call_cb(Event::ChatModified(chat_id)); + context.emit_event(Event::ChatModified(chat_id)); Ok(true) } @@ -2205,7 +2205,7 @@ pub async fn set_muted( .await .is_ok() { - context.call_cb(Event::ChatModified(chat_id)); + context.emit_event(Event::ChatModified(chat_id)); } else { bail!("Failed to set mute duration, chat might not exist -"); } @@ -2285,7 +2285,7 @@ pub async fn remove_contact_from_chat( // removed it first, it would complicate the // check/encryption logic. success = remove_from_chat_contacts_table(context, chat_id, contact_id).await; - context.call_cb(Event::ChatModified(chat_id)); + context.emit_event(Event::ChatModified(chat_id)); } } } @@ -2375,12 +2375,12 @@ pub async fn set_chat_name( msg.param.set(Param::Arg, &chat.name); } msg.id = send_msg(context, chat_id, &mut msg).await?; - context.call_cb(Event::MsgsChanged { + context.emit_event(Event::MsgsChanged { chat_id, msg_id: msg.id, }); } - context.call_cb(Event::ChatModified(chat_id)); + context.emit_event(Event::ChatModified(chat_id)); success = true; } } @@ -2540,7 +2540,7 @@ pub async fn forward_msgs( } } for (chat_id, msg_id) in created_chats.iter().zip(created_msgs.iter()) { - context.call_cb(Event::MsgsChanged { + context.emit_event(Event::MsgsChanged { chat_id: *chat_id, msg_id: *msg_id, }); @@ -2663,7 +2663,7 @@ pub async fn add_device_msg( } if !msg_id.is_unset() { - context.call_cb(Event::IncomingMsg { chat_id, msg_id }); + context.emit_event(Event::IncomingMsg { chat_id, msg_id }); } Ok(msg_id) @@ -2733,7 +2733,7 @@ pub(crate) async fn add_info_msg(context: &Context, chat_id: ChatId, text: impl .get_rowid(context, "msgs", "rfc724_mid", &rfc724_mid) .await .unwrap_or_default(); - context.call_cb(Event::MsgsChanged { + context.emit_event(Event::MsgsChanged { chat_id, msg_id: MsgId::new(row_id), }); diff --git a/src/config.rs b/src/config.rs index 34a5f69b9..9cc1f4edd 100644 --- a/src/config.rs +++ b/src/config.rs @@ -225,7 +225,7 @@ impl Context { Config::DeleteDeviceAfter => { let ret = self.sql.set_raw_config(self, key, value).await; // Force chatlist reload to delete old messages immediately. - self.call_cb(Event::MsgsChanged { + self.emit_event(Event::MsgsChanged { msg_id: MsgId::new(0), chat_id: ChatId::new(0), }); diff --git a/src/configure/mod.rs b/src/configure/mod.rs index bb981f6d4..94b58ab3c 100644 --- a/src/configure/mod.rs +++ b/src/configure/mod.rs @@ -28,7 +28,7 @@ macro_rules! progress { $progress <= 1000, "value in range 0..1000 expected with: 0=error, 1..999=progress, 1000=success" ); - $context.call_cb($crate::events::Event::ConfigureProgress($progress)); + $context.emit_event($crate::events::Event::ConfigureProgress($progress)); }; } diff --git a/src/contact.rs b/src/contact.rs index 9b9dd1c97..0fff01025 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -241,7 +241,7 @@ impl Contact { let (contact_id, sth_modified) = Contact::add_or_lookup(context, name, addr, Origin::ManuallyCreated).await?; let blocked = Contact::is_blocked_load(context, contact_id).await; - context.call_cb(Event::ContactsChanged( + context.emit_event(Event::ContactsChanged( if sth_modified == Modifier::Created { Some(contact_id) } else { @@ -269,7 +269,7 @@ impl Contact { .await .is_ok() { - context.call_cb(Event::MsgsChanged { + context.emit_event(Event::MsgsChanged { chat_id: ChatId::new(0), msg_id: MsgId::new(0), }); @@ -528,7 +528,7 @@ impl Contact { } } if modify_cnt > 0 { - context.call_cb(Event::ContactsChanged(None)); + context.emit_event(Event::ContactsChanged(None)); } Ok(modify_cnt) @@ -777,7 +777,7 @@ impl Contact { .await { Ok(_) => { - context.call_cb(Event::ContactsChanged(None)); + context.emit_event(Event::ContactsChanged(None)); return Ok(()); } Err(err) => { @@ -1054,7 +1054,7 @@ async fn set_block_contact(context: &Context, contact_id: u32, new_blocking: boo paramsv![new_blocking, 100, contact_id as i32], ).await.is_ok() { Contact::mark_noticed(context, contact_id).await; - context.call_cb(Event::ContactsChanged(None)); + context.emit_event(Event::ContactsChanged(None)); } } } @@ -1080,7 +1080,7 @@ pub(crate) async fn set_profile_image( }; if changed { contact.update_param(context).await?; - context.call_cb(Event::ContactsChanged(Some(contact_id))); + context.emit_event(Event::ContactsChanged(Some(contact_id))); } Ok(()) } diff --git a/src/context.rs b/src/context.rs index c3b614e16..57067df8a 100644 --- a/src/context.rs +++ b/src/context.rs @@ -4,10 +4,8 @@ use std::collections::{BTreeMap, HashMap}; use std::ffi::OsString; use std::ops::Deref; -use anyhow::anyhow; use async_std::path::{Path, PathBuf}; use async_std::sync::{channel, Arc, Mutex, Receiver, RwLock, Sender}; -use crossbeam_channel::{unbounded, Receiver as SyncReceiver, Sender as SyncSender}; use crate::chat::*; use crate::config::Config; @@ -15,7 +13,7 @@ use crate::constants::*; use crate::contact::*; use crate::dc_tools::duration_to_str; use crate::error::*; -use crate::events::Event; +use crate::events::{Event, EventEmitter, Events}; use crate::job::{self, Action}; use crate::key::{DcKey, Key, SignedPublicKey}; use crate::login_param::LoginParam; @@ -55,7 +53,7 @@ pub struct InnerContext { /// Mutex to enforce only a single running oauth2 is running. pub(crate) oauth2_mutex: Mutex<()>, pub(crate) translated_stockstrings: RwLock>, - pub(crate) logs: (SyncSender, SyncReceiver), + pub(crate) events: Events, pub(crate) scheduler: RwLock, @@ -121,7 +119,7 @@ impl Context { generating_key_mutex: Mutex::new(()), oauth2_mutex: Mutex::new(()), translated_stockstrings: RwLock::new(HashMap::new()), - logs: unbounded(), + events: Events::default(), scheduler: RwLock::new(Scheduler::Stopped), creation_time: std::time::SystemTime::now(), }; @@ -172,14 +170,14 @@ impl Context { self.blobdir.as_path() } - pub fn call_cb(&self, event: Event) { - self.logs.0.send(event).expect("failed to send event"); + /// Emits a single event. + pub fn emit_event(&self, event: Event) { + self.events.emit(event); } - pub fn get_next_event(&self) -> Result { - let event = self.logs.1.recv().map_err(|err| anyhow!("{}", err))?; - - Ok(event) + /// Get the next queued event. + pub fn get_event_emitter(&self) -> EventEmitter { + self.events.get_emitter() } // Ongoing process allocation/free/check diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index 3219e908b..bcb6e39a2 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -94,7 +94,7 @@ pub async fn dc_receive_imf( CreateEvent::MsgsChanged => Event::MsgsChanged { msg_id, chat_id }, CreateEvent::IncomingMsg => Event::IncomingMsg { msg_id, chat_id }, }; - context.call_cb(event); + context.emit_event(event); } } }; @@ -197,7 +197,7 @@ pub async fn dc_receive_imf( if let Some(avatar_action) = &mime_parser.user_avatar { match contact::set_profile_image(&context, from_id, avatar_action).await { Ok(()) => { - context.call_cb(Event::ChatModified(chat_id)); + context.emit_event(Event::ChatModified(chat_id)); } Err(err) => { warn!(context, "reveive_imf cannot update profile image: {}", err); @@ -806,7 +806,7 @@ async fn save_locations( } } if send_event { - context.call_cb(Event::LocationChanged(Some(from_id))); + context.emit_event(Event::LocationChanged(Some(from_id))); } } @@ -1120,7 +1120,7 @@ async fn create_or_lookup_group( .await .is_ok() { - context.call_cb(Event::ChatModified(chat_id)); + context.emit_event(Event::ChatModified(chat_id)); } } } @@ -1179,7 +1179,7 @@ async fn create_or_lookup_group( } if send_EVENT_CHAT_MODIFIED { - context.call_cb(Event::ChatModified(chat_id)); + context.emit_event(Event::ChatModified(chat_id)); } Ok((chat_id, chat_id_blocked)) } @@ -1300,7 +1300,7 @@ async fn create_or_lookup_adhoc_group( chat::add_to_chat_contacts_table(context, new_chat_id, member_id).await; } - context.call_cb(Event::ChatModified(new_chat_id)); + context.emit_event(Event::ChatModified(new_chat_id)); Ok((new_chat_id, create_blocked)) } diff --git a/src/dc_tools.rs b/src/dc_tools.rs index 14286b9fd..63fe7626f 100644 --- a/src/dc_tools.rs +++ b/src/dc_tools.rs @@ -283,7 +283,7 @@ pub(crate) async fn dc_delete_file(context: &Context, path: impl AsRef) -> let dpath = format!("{}", path.as_ref().to_string_lossy()); match fs::remove_file(path_abs).await { Ok(_) => { - context.call_cb(Event::DeletedBlobFile(dpath)); + context.emit_event(Event::DeletedBlobFile(dpath)); true } Err(err) => { diff --git a/src/events.rs b/src/events.rs index bcab73e8b..c0e1e5901 100644 --- a/src/events.rs +++ b/src/events.rs @@ -1,12 +1,66 @@ //! # Events specification use async_std::path::PathBuf; +use crossbeam_channel::{bounded as channel, Receiver, Sender, TrySendError}; use strum::EnumProperty; use crate::chat::ChatId; use crate::message::MsgId; +#[derive(Debug)] +pub struct Events { + receiver: Receiver, + sender: Sender, +} + +impl Default for Events { + fn default() -> Self { + let (sender, receiver) = channel(1_000); + + Self { receiver, sender } + } +} + +impl Events { + pub fn emit(&self, event: Event) { + match self.sender.try_send(event) { + Ok(()) => {} + Err(TrySendError::Full(event)) => { + // when we are full, we pop remove the oldest event and push on the new one + let _ = self.receiver.try_recv(); + + // try again + self.emit(event); + } + Err(TrySendError::Disconnected(_)) => { + unreachable!("unable to emit event, channel disconnected"); + } + } + } + + /// Retrieve the event emitter. + pub fn get_emitter(&self) -> EventEmitter { + EventEmitter(self.receiver.clone()) + } +} + +#[derive(Debug, Clone)] +pub struct EventEmitter(Receiver); + +impl EventEmitter { + /// Blocking recv of an event. Return `None` if the `Sender` has been droped. + pub fn recv_sync(&self) -> Option { + self.0.recv().ok() + } + + /// Blocking async recv of an event. Return `None` if the `Sender` has been droped. + pub async fn recv(&self) -> Option { + // TODO: change once we can use async channels internally. + self.0.recv().ok() + } +} + impl Event { /// Returns the corresponding Event id. pub fn as_id(&self) -> i32 { diff --git a/src/imex.rs b/src/imex.rs index 15eb7ab54..2c443e846 100644 --- a/src/imex.rs +++ b/src/imex.rs @@ -377,7 +377,7 @@ async fn imex_inner( ensure!(param.is_some(), "No Import/export dir/file given."); info!(context, "Import/export process started."); - context.call_cb(Event::ImexProgress(10)); + context.emit_event(Event::ImexProgress(10)); ensure!(context.sql.is_open().await, "Database not opened."); @@ -401,11 +401,11 @@ async fn imex_inner( match success { Ok(()) => { info!(context, "IMEX successfully completed"); - context.call_cb(Event::ImexProgress(1000)); + context.emit_event(Event::ImexProgress(1000)); Ok(()) } Err(err) => { - context.call_cb(Event::ImexProgress(0)); + context.emit_event(Event::ImexProgress(0)); bail!("IMEX FAILED to complete: {}", err); } } @@ -489,7 +489,7 @@ async fn import_backup(context: &Context, backup_to_import: impl AsRef) -> if permille > 990 { permille = 990 } - context.call_cb(Event::ImexProgress(permille)); + context.emit_event(Event::ImexProgress(permille)); if file_blob.is_empty() { continue; } @@ -565,7 +565,7 @@ async fn export_backup(context: &Context, dir: impl AsRef) -> Result<()> { dest_sql .set_raw_config_int(context, "backup_time", now as i32) .await?; - context.call_cb(Event::ImexFileWritten(dest_path_filename)); + context.emit_event(Event::ImexFileWritten(dest_path_filename)); Ok(()) } }; @@ -604,7 +604,7 @@ async fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> { } processed_files_cnt += 1; let permille = max(min(processed_files_cnt * 1000 / total_files_cnt, 990), 10); - context.call_cb(Event::ImexProgress(permille)); + context.emit_event(Event::ImexProgress(permille)); let name_f = entry.file_name(); let name = name_f.to_string_lossy(); @@ -761,7 +761,7 @@ async fn export_key_to_asc_file( if res.is_err() { error!(context, "Cannot write key to {}", file_name.display()); } else { - context.call_cb(Event::ImexFileWritten(file_name)); + context.emit_event(Event::ImexFileWritten(file_name)); } res } diff --git a/src/job.rs b/src/job.rs index fd4839b4d..98a7a3ad4 100644 --- a/src/job.rs +++ b/src/job.rs @@ -691,7 +691,7 @@ async fn set_delivered(context: &Context, msg_id: MsgId) { ) .await .unwrap_or_default(); - context.call_cb(Event::MsgDelivered { chat_id, msg_id }); + context.emit_event(Event::MsgDelivered { chat_id, msg_id }); } // special case for DC_JOB_SEND_MSG_TO_SMTP diff --git a/src/location.rs b/src/location.rs index fee937438..d36450bbf 100644 --- a/src/location.rs +++ b/src/location.rs @@ -227,7 +227,7 @@ pub async fn send_locations_to_chat(context: &Context, chat_id: ChatId, seconds: .await; chat::add_info_msg(context, chat_id, stock_str).await; } - context.call_cb(Event::ChatModified(chat_id)); + context.emit_event(Event::ChatModified(chat_id)); if 0 != seconds { schedule_maybe_send_locations(context, false).await; job::add( @@ -302,7 +302,7 @@ pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64 } } if continue_streaming { - context.call_cb(Event::LocationChanged(Some(DC_CONTACT_ID_SELF))); + context.emit_event(Event::LocationChanged(Some(DC_CONTACT_ID_SELF))); }; schedule_maybe_send_locations(context, false).await; } @@ -382,7 +382,7 @@ pub async fn delete_all(context: &Context) -> Result<(), Error> { .sql .execute("DELETE FROM locations;", paramsv![]) .await?; - context.call_cb(Event::LocationChanged(None)); + context.emit_event(Event::LocationChanged(None)); Ok(()) } @@ -714,7 +714,7 @@ pub(crate) async fn job_maybe_send_locations_ended( .stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0) .await; chat::add_info_msg(context, chat_id, stock_str).await; - context.call_cb(Event::ChatModified(chat_id)); + context.emit_event(Event::ChatModified(chat_id)); } } job::Status::Finished(Ok(())) diff --git a/src/log.rs b/src/log.rs index db56c1f70..38e3c2f9e 100644 --- a/src/log.rs +++ b/src/log.rs @@ -44,6 +44,6 @@ macro_rules! error { #[macro_export] macro_rules! emit_event { ($ctx:expr, $event:expr) => { - $ctx.call_cb($event); + $ctx.emit_event($event); }; } diff --git a/src/message.rs b/src/message.rs index 8e63fc602..59951b265 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1030,7 +1030,7 @@ pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) { } if !msg_ids.is_empty() { - context.call_cb(Event::MsgsChanged { + context.emit_event(Event::MsgsChanged { chat_id: ChatId::new(0), msg_id: MsgId::new(0), }); @@ -1112,7 +1112,7 @@ pub async fn markseen_msgs(context: &Context, msg_ids: Vec) -> bool { } if send_event { - context.call_cb(Event::MsgsChanged { + context.emit_event(Event::MsgsChanged { chat_id: ChatId::new(0), msg_id: MsgId::new(0), }); @@ -1270,7 +1270,7 @@ pub async fn set_msg_failed(context: &Context, msg_id: MsgId, error: Option= 0 && $progress <= 1000, "value in range 0..1000 expected with: 0=error, 1..999=progress, 1000=success" ); - $context.call_cb($crate::events::Event::SecurejoinJoinerProgress { + $context.emit_event($crate::events::Event::SecurejoinJoinerProgress { contact_id: $contact_id, progress: $progress, }); @@ -45,7 +45,7 @@ macro_rules! inviter_progress { $progress >= 0 && $progress <= 1000, "value in range 0..1000 expected with: 0=error, 1..999=progress, 1000=success" ); - $context.call_cb($crate::events::Event::SecurejoinInviterProgress { + $context.emit_event($crate::events::Event::SecurejoinInviterProgress { contact_id: $contact_id, progress: $progress, }); diff --git a/src/smtp/mod.rs b/src/smtp/mod.rs index cf57df20e..3a4188d6a 100644 --- a/src/smtp/mod.rs +++ b/src/smtp/mod.rs @@ -98,7 +98,7 @@ impl Smtp { } if lp.send_server.is_empty() || lp.send_port == 0 { - context.call_cb(Event::ErrorNetwork("SMTP bad parameters.".into())); + context.emit_event(Event::ErrorNetwork("SMTP bad parameters.".into())); return Err(Error::BadParameters); } @@ -183,7 +183,7 @@ impl Smtp { self.transport = Some(trans); self.last_success = Some(Instant::now()); - context.call_cb(Event::SmtpConnected(format!( + context.emit_event(Event::SmtpConnected(format!( "SMTP-LOGIN as {} ok", lp.send_user, ))); diff --git a/src/smtp/send.rs b/src/smtp/send.rs index cade602f2..350d705e5 100644 --- a/src/smtp/send.rs +++ b/src/smtp/send.rs @@ -49,7 +49,7 @@ impl Smtp { if let Some(ref mut transport) = self.transport { transport.send(mail).await.map_err(Error::SendError)?; - context.call_cb(Event::SmtpMessageSent(format!( + context.emit_event(Event::SmtpMessageSent(format!( "Message len={} was smtp-sent to {}", message_len, recipients_display ))); From c53a3d5cb4d2b335fb1a5d7c4d2ae7e449b1b09b Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Fri, 22 May 2020 22:04:20 +0200 Subject: [PATCH 089/118] update rust api to match ffi --- deltachat-ffi/src/lib.rs | 7 +++---- examples/repl/main.rs | 16 +++++++--------- examples/simple.rs | 13 ++++++------- src/context.rs | 27 +++++++++++++++------------ src/scheduler.rs | 2 +- 5 files changed, 32 insertions(+), 33 deletions(-) diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 17d1aed7f..203b70ac4 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -487,9 +487,8 @@ pub unsafe extern "C" fn dc_start_io(context: *mut dc_context_t) { } let ffi_context = &*context; - with_inner_async!(ffi_context, ctx, { ctx.run() }).unwrap_or(()) + with_inner_async!(ffi_context, ctx, { ctx.start_io() }).unwrap_or(()) } - #[no_mangle] pub unsafe extern "C" fn dc_is_io_running(context: *mut dc_context_t) -> libc::c_int { if context.is_null() { @@ -497,7 +496,7 @@ pub unsafe extern "C" fn dc_is_io_running(context: *mut dc_context_t) -> libc::c } let ffi_context = &*context; - with_inner_async!(ffi_context, ctx, { ctx.is_running() }).unwrap_or_default() as libc::c_int + with_inner_async!(ffi_context, ctx, { ctx.is_io_running() }).unwrap_or_default() as libc::c_int } #[no_mangle] @@ -698,7 +697,7 @@ pub unsafe extern "C" fn dc_stop_io(context: *mut dc_context_t) { let ffi_context = &*context; with_inner_async!(ffi_context, ctx, async move { - ctx.stop().await; + ctx.stop_io().await; }) .unwrap_or(()) } diff --git a/examples/repl/main.rs b/examples/repl/main.rs index 86a2088f6..e49844463 100644 --- a/examples/repl/main.rs +++ b/examples/repl/main.rs @@ -277,10 +277,8 @@ async fn start(args: Vec) -> Result<(), Error> { let events = context.get_event_emitter(); async_std::task::spawn(async move { - loop { - if let Some(event) = events.recv().await { - receive_event(event); - } + while let Some(event) = events.recv().await { + receive_event(event); } }); @@ -322,7 +320,7 @@ async fn start(args: Vec) -> Result<(), Error> { } Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => { println!("Exiting..."); - context.stop().await; + context.stop_io().await; break; } Err(err) => { @@ -355,10 +353,10 @@ async fn handle_cmd( match arg0 { "connect" => { - ctx.run().await; + ctx.start_io().await; } "disconnect" => { - ctx.stop().await; + ctx.stop_io().await; } "configure" => { ctx.configure().await?; @@ -381,7 +379,7 @@ async fn handle_cmd( print!("\x1b[1;1H\x1b[2J"); } "getqr" | "getbadqr" => { - ctx.run().await; + ctx.start_io().await; if let Some(mut qr) = dc_get_securejoin_qr(&ctx, ChatId::new(arg1.parse().unwrap_or_default())).await { @@ -400,7 +398,7 @@ async fn handle_cmd( } } "joinqr" => { - ctx.run().await; + ctx.start_io().await; if !arg0.is_empty() { dc_join_securejoin(&ctx, arg1).await; } diff --git a/examples/simple.rs b/examples/simple.rs index 694d901f0..3d8dc6cba 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -39,11 +39,9 @@ async fn main() { println!("info: {:#?}", info); let events = ctx.get_event_emitter(); - async_std::task::spawn(async move { - loop { - if let Some(event) = events.recv().await { - cb(event); - } + let events_spawn = async_std::task::spawn(async move { + while let Some(event) = events.recv().await { + cb(event); } }); @@ -62,7 +60,7 @@ async fn main() { ctx.configure().await.unwrap(); println!("------ RUN ------"); - ctx.clone().run().await; + ctx.clone().start_io().await; println!("--- SENDING A MESSAGE ---"); let contact_id = Contact::create(&ctx, "dignifiedquire", "dignifiedquire@gmail.com") @@ -89,6 +87,7 @@ async fn main() { async_std::task::sleep(duration).await; println!("stopping"); - ctx.stop().await; + ctx.stop_io().await; println!("closing"); + events_spawn.await; } diff --git a/src/context.rs b/src/context.rs index 57067df8a..1fa3d6c8f 100644 --- a/src/context.rs +++ b/src/context.rs @@ -135,21 +135,24 @@ impl Context { Ok(ctx) } - pub async fn run(&self) { - assert!(!self.is_running().await, "context is already running"); + /// Starts the IO scheduler. + pub async fn start_io(&self) { + info!(self, "starting IO"); + assert!(!self.is_io_running().await, "context is already running"); let l = &mut *self.inner.scheduler.write().await; - l.run(self.clone()).await; + l.start(self.clone()).await; } - pub async fn is_running(&self) -> bool { - self.inner.is_running().await + /// Returns if the IO scheduler is running. + pub async fn is_io_running(&self) -> bool { + self.inner.is_io_running().await } - pub async fn stop(&self) { - info!(self, "stopping context"); - self.inner.stop().await; - info!(self, "stopped context"); + /// Stops the IO scheduler. + pub async fn stop_io(&self) { + info!(self, "stopping IO"); + self.inner.stop_io().await; } /// Returns a reference to the underlying SQL instance. @@ -491,12 +494,12 @@ impl Context { } impl InnerContext { - async fn is_running(&self) -> bool { + async fn is_io_running(&self) -> bool { self.scheduler.read().await.is_running() } - async fn stop(&self) { - assert!(self.is_running().await, "context is already stopped"); + async fn stop_io(&self) { + assert!(self.is_io_running().await, "context is already stopped"); let token = { let lock = &*self.scheduler.read().await; lock.pre_stop().await diff --git a/src/scheduler.rs b/src/scheduler.rs index b9f9b5220..c1c84f193 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -276,7 +276,7 @@ async fn smtp_loop(ctx: Context, started: Sender<()>, smtp_handlers: SmtpConnect impl Scheduler { /// Start the scheduler, panics if it is already running. - pub async fn run(&mut self, ctx: Context) { + pub async fn start(&mut self, ctx: Context) { let (mvbox, mvbox_handlers) = ImapConnectionState::new(); let (sentbox, sentbox_handlers) = ImapConnectionState::new(); let (smtp, smtp_handlers) = SmtpConnectionState::new(); From 2b1d4651fb694b7d44244bdc7fcef5a2caf26ca4 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Fri, 22 May 2020 22:40:55 +0200 Subject: [PATCH 090/118] remove dc_open and dc_close --- deltachat-ffi/deltachat.h | 65 +-- deltachat-ffi/src/lib.rs | 810 ++++++++++++-------------------------- 2 files changed, 261 insertions(+), 614 deletions(-) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index de84d9699..5dc6d343c 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -20,7 +20,7 @@ typedef struct _dc_contact dc_contact_t; typedef struct _dc_lot dc_lot_t; typedef struct _dc_provider dc_provider_t; typedef struct _dc_event dc_event_t; - typedef struct _dc_event_emitter dc_event_emitter_t; +typedef struct _dc_event_emitter dc_event_emitter_t; /** @@ -236,8 +236,11 @@ void dc_event_unref (dc_event_t* event); * - The callback SHOULD return _fast_, for GUI updates etc. you should * post yourself an asynchronous message to your GUI thread, if needed. * - events do not expect a return value, just always return 0. - * @param userdata can be used by the client for any purpuse. He finds it - * later in dc_get_userdata(). + * @param dbfile The file to use to store the database, something like `~/file` won't + * work on all systems, if in doubt, use absolute paths. + * @param blobdir A directory to store the blobs in; a trailing slash is not needed. + * If you pass NULL or the empty string, deltachat-core creates a directory + * beside _dbfile_ with the same name and the suffix `-blobs`. * @param os_name is only for decorative use * and is shown eg. in the `X-Mailer:` header * in the form "Delta Chat Core /". @@ -249,7 +252,7 @@ void dc_event_unref (dc_event_t* event); * The object must be passed to the other context functions * and must be freed using dc_context_unref() after usage. */ -dc_context_t* dc_context_new (void* userdata, const char* os_name); +dc_context_t* dc_context_new (const char* os_name, const char* dbfile, const char* blobdir); /** @@ -266,60 +269,6 @@ dc_context_t* dc_context_new (void* userdata, const char* os_nam */ void dc_context_unref (dc_context_t* context); - -/** - * Get user data associated with a context object. - * - * @memberof dc_context_t - * @param context The context object as created by dc_context_new(). - * @return User data, this is the second parameter given to dc_context_new(). - */ -void* dc_get_userdata (dc_context_t* context); - - -/** - * Open context database. If the given file does not exist, it is - * created and can be set up using dc_set_config() afterwards. - * - * @memberof dc_context_t - * @param context The context object as created by dc_context_new(). - * @param dbfile The file to use to store the database, something like `~/file` won't - * work on all systems, if in doubt, use absolute paths. - * @param blobdir A directory to store the blobs in; a trailing slash is not needed. - * If you pass NULL or the empty string, deltachat-core creates a directory - * beside _dbfile_ with the same name and the suffix `-blobs`. - * @return 1 on success, 0 on failure - * eg. if the file is not writable - * or if there is already a database opened for the context. - */ -int dc_open (dc_context_t* context, const char* dbfile, const char* blobdir); - - -/** - * Close context database opened by dc_open(). - * Before this, connections to SMTP and IMAP are closed; these connections - * are started automatically as needed eg. by sending for fetching messages. - * This function is also implicitly called by dc_context_unref(). - * Multiple calls to this functions are okay, the function takes care not - * to free objects twice. - * - * @memberof dc_context_t - * @param context The context object as created by dc_context_new(). - * @return None. - */ -void dc_close (dc_context_t* context); - - -/** - * Check if the context database is open. - * - * @memberof dc_context_t - * @param context The context object as created by dc_context_new(). - * @return 0=context is not open, 1=context is open. - */ -int dc_is_open (const dc_context_t* context); - - /** * Get the blob directory. * diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 203b70ac4..f996db069 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -18,12 +18,9 @@ use std::convert::TryInto; use std::fmt::Write; use std::ptr; use std::str::FromStr; -use std::sync::{Arc, RwLock}; use std::time::{Duration, SystemTime}; -use anyhow::anyhow; -use async_std::task::block_on; -use libc::uintptr_t; +use async_std::task::{block_on, spawn}; use num_traits::{FromPrimitive, ToPrimitive}; use deltachat::chat::{ChatId, ChatVisibility, MuteDuration}; @@ -52,156 +49,44 @@ use self::string::*; // dc_context_t -/// The FFI context struct. -/// -/// This structure represents the [Context] on the FFI interface. -/// Since it is returned by [dc_context_new] before it is initialised -/// by [dc_open] it needs to store the actual [Context] in an [Option] -/// and protected by an [RwLock]. Other than that it needs to store -/// the data which is passed into [dc_context_new]. -pub struct ContextWrapper { - userdata: *mut libc::c_void, - os_name: String, - inner: Arc>>, -} - -unsafe impl Send for ContextWrapper {} -unsafe impl Sync for ContextWrapper {} - -/// Callback function that should be given to [dc_context_new]. -/// -/// @memberof [dc_context_t] -/// @param context The context object as returned by [dc_context_new]. -/// @param event one of the @ref DC_EVENT constants -/// @param data1 depends on the event parameter -/// @param data2 depends on the event parameter -/// @return return 0 unless stated otherwise in the event parameter documentation -pub type dc_callback_t = - unsafe extern "C" fn(_: &dc_context_t, _: i32, _: uintptr_t, _: uintptr_t) -> uintptr_t; - /// Struct representing the deltachat context. -/// -/// See [ContextWrapper] for implementation details. -pub type dc_context_t = ContextWrapper; - -impl ContextWrapper { - /// Log a warning on the FFI context. - /// - /// Like [error] but logs as a warning which only goes to the - /// logfile rather than being shown directly to the user. - unsafe fn warning(&self, msg: &str) { - self.with_inner(|ctx| { - ctx.emit_event(Event::Warning(msg.to_string())); - }) - .unwrap(); - } - - /// Unlock the context and execute a closure with it. - /// - /// This unlocks the context and gets a read lock. The Rust - /// [Context] object it passed as only argument to the closure - /// which can now do Rust API calls using it. The return value of - /// the closure will be returned by this function. When the - /// closure returns the read lock is released. - /// - /// If the context is not open an error is logged via the callback - /// and `Err(())` is returned. - /// - /// This function returns a [Result] allowing the caller to supply - /// the appropriate return value for an error return since this - /// differs for various functions on the FFI API: sometimes 0, - /// NULL, an empty string etc. - fn with_inner(&self, ctxfn: F) -> Result - where - F: FnOnce(&Context) -> T, - { - let guard = self.inner.read().unwrap(); - match guard.as_ref() { - Some(ref ctx) => Ok(ctxfn(ctx)), - None => { - eprintln!("ignoring careless call to non open context"); - Err(()) - } - } - } - - fn is_open(&self) -> bool { - self.inner.read().unwrap().is_some() - } -} - -macro_rules! with_inner_async { - ($ctx:expr, $name:ident, $block:expr) => {{ - let l = $ctx.inner.clone(); - let lock = l.read().unwrap(); - block_on(async move { - match lock.as_ref() { - Some(ctx) => { - let $name = ctx; - let res = $block.await; - Ok(res) - } - None => { - eprintln!("ignoring careless call to non open context"); - Err(()) - } - } - }) - }}; -} - -macro_rules! with_inner_spawn { - ($ctx:expr, $name:ident, $block:expr) => {{ - let l = $ctx.inner.clone(); - let lock = l.read().unwrap(); - match lock.as_ref() { - Some(ctx) => { - let $name = ctx.clone(); - async_std::task::spawn(async move { $block.await }); - Ok(()) - } - None => { - eprintln!("ignoring careless call to non open context"); - Err(()) - } - } - }}; -} -macro_rules! try_inner_async { - ($ctx:expr, $name:ident, $block:expr) => {{ - let l = $ctx.inner.clone(); - let lock = l.read().unwrap(); - block_on(async move { - match lock.as_ref() { - Some(ctx) => { - let $name = ctx; - $block.await - } - None => Err(anyhow!("context not open")), - } - }) - }}; -} +pub type dc_context_t = Context; #[no_mangle] pub unsafe extern "C" fn dc_context_new( - userdata: *mut libc::c_void, os_name: *const libc::c_char, + dbfile: *const libc::c_char, + blobdir: *const libc::c_char, ) -> *mut dc_context_t { setup_panic!(); + if dbfile.is_null() { + eprintln!("ignoring careless call to dc_context_new()"); + return ptr::null_mut(); + } + let os_name = if os_name.is_null() { String::from("DcFFI") } else { to_string_lossy(os_name) }; - let ffi_ctx = ContextWrapper { - userdata, - os_name, - inner: Arc::new(RwLock::new(None)), - }; - Box::into_raw(Box::new(ffi_ctx)) + let ctx = if blobdir.is_null() || *blobdir == 0 { + block_on(Context::new(os_name, as_path(dbfile).to_path_buf().into())) + } else { + block_on(Context::with_blobdir( + os_name, + as_path(dbfile).to_path_buf().into(), + as_path(blobdir).to_path_buf().into(), + )) + }; + match ctx { + Ok(ctx) => Box::into_raw(Box::new(ctx)), + Err(err) => { + eprintln!("failed to create context: {}", err); + ptr::null_mut() + } + } } /// Release the context structure. @@ -213,72 +98,7 @@ pub unsafe extern "C" fn dc_context_unref(context: *mut dc_context_t) { eprintln!("ignoring careless call to dc_context_unref()"); return; } - let ffi_context = &mut *context; - Box::from_raw(ffi_context); -} - -#[no_mangle] -pub unsafe extern "C" fn dc_get_userdata(context: *mut dc_context_t) -> *mut libc::c_void { - if context.is_null() { - eprintln!("ignoring careless call to dc_get_userdata()"); - return ptr::null_mut(); - } - let ffi_context = &mut *context; - ffi_context.userdata -} - -#[no_mangle] -pub unsafe extern "C" fn dc_open( - context: *mut dc_context_t, - dbfile: *const libc::c_char, - blobdir: *const libc::c_char, -) -> libc::c_int { - if context.is_null() || dbfile.is_null() { - eprintln!("ignoring careless call to dc_open()"); - return 0; - } - let ffi_context = &*context; - - let ctx = if blobdir.is_null() || *blobdir == 0 { - block_on(Context::new( - ffi_context.os_name.clone(), - as_path(dbfile).to_path_buf().into(), - )) - } else { - block_on(Context::with_blobdir( - ffi_context.os_name.clone(), - as_path(dbfile).to_path_buf().into(), - as_path(blobdir).to_path_buf().into(), - )) - }; - match ctx { - Ok(ctx) => { - let mut inner_guard = ffi_context.inner.write().unwrap(); - *inner_guard = Some(ctx); - 1 - } - Err(_) => 0, - } -} - -#[no_mangle] -pub unsafe extern "C" fn dc_close(context: *mut dc_context_t) { - if context.is_null() { - eprintln!("ignoring careless call to dc_close()"); - return; - } - let ffi_context = &mut *context; - ffi_context.inner.write().unwrap().take(); -} - -#[no_mangle] -pub unsafe extern "C" fn dc_is_open(context: *mut dc_context_t) -> libc::c_int { - if context.is_null() { - eprintln!("ignoring careless call to dc_is_open()"); - return 0; - } - let ffi_context = &*context; - ffi_context.is_open() as libc::c_int + Box::from_raw(context); } #[no_mangle] @@ -287,10 +107,8 @@ pub unsafe extern "C" fn dc_get_blobdir(context: *mut dc_context_t) -> *mut libc eprintln!("ignoring careless call to dc_get_blobdir()"); return "".strdup(); } - let ffi_context = &*context; - ffi_context - .with_inner(|ctx| ctx.get_blobdir().to_string_lossy().strdup()) - .unwrap_or_else(|_| "".strdup()) + let ctx = &*context; + ctx.get_blobdir().to_string_lossy().strdup() } #[no_mangle] @@ -303,18 +121,17 @@ pub unsafe extern "C" fn dc_set_config( eprintln!("ignoring careless call to dc_set_config()"); return 0; } - let ffi_context = &*context; + let ctx = &*context; match config::Config::from_str(&to_string_lossy(key)) { // When ctx.set_config() fails it already logged the error. // TODO: Context::set_config() should not log this - Ok(key) => with_inner_async!(ffi_context, ctx, async move { + Ok(key) => block_on(async move { ctx.set_config(key, to_opt_string_lossy(value).as_ref().map(|x| x.as_str())) .await .is_ok() as libc::c_int - }) - .unwrap_or(0), + }), Err(_) => { - ffi_context.warning("dc_set_config(): invalid key"); + warn!(ctx, "dc_set_config(): invalid key"); 0 } } @@ -329,14 +146,11 @@ pub unsafe extern "C" fn dc_get_config( eprintln!("ignoring careless call to dc_get_config()"); return "".strdup(); } - let ffi_context = &*context; + let ctx = &*context; match config::Config::from_str(&to_string_lossy(key)) { - Ok(key) => with_inner_async!(ffi_context, ctx, async move { - ctx.get_config(key).await.unwrap_or_default().strdup() - }) - .unwrap_or_else(|_| "".strdup()), + Ok(key) => block_on(async move { ctx.get_config(key).await.unwrap_or_default().strdup() }), Err(_) => { - ffi_context.warning("dc_get_config(): invalid key"); + warn!(ctx, "dc_get_config(): invalid key"); "".strdup() } } @@ -353,9 +167,9 @@ pub unsafe extern "C" fn dc_set_stock_translation( return 0; } let msg = to_string_lossy(stock_msg); - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { match StockMessage::from_u32(stock_id) { Some(id) => match ctx.set_stock_translation(id, msg).await { Ok(()) => 1, @@ -370,7 +184,6 @@ pub unsafe extern "C" fn dc_set_stock_translation( } } }) - .unwrap_or(0) } #[no_mangle] @@ -383,9 +196,9 @@ pub unsafe extern "C" fn dc_set_config_from_qr( return 0; } let qr = to_string_lossy(qr); - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { match qr::set_config_from_qr(&ctx, &qr).await { Ok(()) => 1, Err(err) => { @@ -394,7 +207,6 @@ pub unsafe extern "C" fn dc_set_config_from_qr( } } }) - .unwrap_or(0) } #[no_mangle] @@ -403,14 +215,9 @@ pub unsafe extern "C" fn dc_get_info(context: *mut dc_context_t) -> *mut libc::c eprintln!("ignoring careless call to dc_get_info()"); return "".strdup(); } - let ffi_context = &*context; - - let guard = ffi_context.inner.read().unwrap(); + let ctx = &*context; block_on(async move { - let info = match guard.as_ref() { - Some(ref ctx) => ctx.get_info().await, - None => context::get_info(), - }; + let info = ctx.get_info().await; render_info(info).unwrap_or_default().strdup() }) } @@ -436,17 +243,16 @@ pub unsafe extern "C" fn dc_get_oauth2_url( eprintln!("ignoring careless call to dc_get_oauth2_url()"); return ptr::null_mut(); // NULL explicitly defined as "unknown" } - let ffi_context = &*context; + let ctx = &*context; let addr = to_string_lossy(addr); let redirect = to_string_lossy(redirect); - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { match oauth2::dc_get_oauth2_url(&ctx, addr, redirect).await { Some(res) => res.strdup(), None => ptr::null_mut(), } }) - .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -456,14 +262,9 @@ pub unsafe extern "C" fn dc_configure(context: *mut dc_context_t) { return; } - let ffi_context = &*context; + let ctx = &*context; - with_inner_spawn!(ffi_context, ctx, async move { - ctx.configure() - .await - .log_err(ffi_context, "Configure failed") - }) - .ok(); + spawn(async move { ctx.configure().await.log_err(ctx, "Configure failed") }); } #[no_mangle] @@ -472,12 +273,9 @@ pub unsafe extern "C" fn dc_is_configured(context: *mut dc_context_t) -> libc::c eprintln!("ignoring careless call to dc_is_configured()"); return 0; } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { - ctx.is_configured().await as libc::c_int - }) - .unwrap_or(0) + block_on(async move { ctx.is_configured().await as libc::c_int }) } #[no_mangle] @@ -485,18 +283,19 @@ pub unsafe extern "C" fn dc_start_io(context: *mut dc_context_t) { if context.is_null() { return; } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, { ctx.start_io() }).unwrap_or(()) + block_on({ ctx.start_io() }) } + #[no_mangle] pub unsafe extern "C" fn dc_is_io_running(context: *mut dc_context_t) -> libc::c_int { if context.is_null() { return 0; } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, { ctx.is_io_running() }).unwrap_or_default() as libc::c_int + block_on({ ctx.is_io_running() }) as libc::c_int } #[no_mangle] @@ -659,10 +458,8 @@ pub unsafe extern "C" fn dc_get_event_emitter( eprintln!("ignoring careless call to dc_get_event_emitter()"); return ptr::null_mut(); } - let ffi_context = &*context; - ffi_context - .with_inner(|ctx| Box::into_raw(Box::new(ctx.get_event_emitter()))) - .unwrap_or_else(|_| ptr::null_mut()) + let ctx = &*context; + Box::into_raw(Box::new(ctx.get_event_emitter())) } #[no_mangle] @@ -694,12 +491,11 @@ pub unsafe extern "C" fn dc_stop_io(context: *mut dc_context_t) { eprintln!("ignoring careless call to dc_shutdown()"); return; } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { ctx.stop_io().await; }) - .unwrap_or(()) } #[no_mangle] @@ -708,9 +504,9 @@ pub unsafe extern "C" fn dc_maybe_network(context: *mut dc_context_t) { eprintln!("ignoring careless call to dc_maybe_network()"); return; } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { ctx.maybe_network().await }).unwrap_or(()) + block_on(async move { ctx.maybe_network().await }) } #[no_mangle] @@ -724,8 +520,8 @@ pub unsafe extern "C" fn dc_preconfigure_keypair( eprintln!("ignoring careless call to dc_preconfigure_keypair()"); return 0; } - let ffi_context = &*context; - try_inner_async!(ffi_context, ctx, async move { + let ctx = &*context; + block_on(async move { let addr = dc_tools::EmailAddress::new(&to_string_lossy(addr))?; let public = key::SignedPublicKey::from_base64(&to_string_lossy(public_data))?; let secret = key::SignedSecretKey::from_base64(&to_string_lossy(secret_data))?; @@ -735,9 +531,9 @@ pub unsafe extern "C" fn dc_preconfigure_keypair( secret, }; key::store_self_keypair(&ctx, &keypair, key::KeyPairUse::Default).await?; - Ok(1) + Ok::<_, anyhow::Error>(1) }) - .log_err(ffi_context, "Failed to save keypair") + .log_err(ctx, "Failed to save keypair") .unwrap_or(0) } @@ -752,12 +548,12 @@ pub unsafe extern "C" fn dc_get_chatlist( eprintln!("ignoring careless call to dc_get_chatlist()"); return ptr::null_mut(); } - let ffi_context = &*context; + let ctx = &*context; let qs = to_opt_string_lossy(query_str); let qi = if query_id == 0 { None } else { Some(query_id) }; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { match chatlist::Chatlist::try_load( &ctx, flags as usize, @@ -773,7 +569,6 @@ pub unsafe extern "C" fn dc_get_chatlist( Err(_) => ptr::null_mut(), } }) - .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -782,16 +577,15 @@ pub unsafe extern "C" fn dc_create_chat_by_msg_id(context: *mut dc_context_t, ms eprintln!("ignoring careless call to dc_create_chat_by_msg_id()"); return 0; } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { chat::create_by_msg_id(&ctx, MsgId::new(msg_id)) .await - .log_err(ffi_context, "Failed to create chat from msg_id") + .log_err(ctx, "Failed to create chat from msg_id") .map(|id| id.to_u32()) .unwrap_or(0) }) - .unwrap_or(0) } #[no_mangle] @@ -803,16 +597,15 @@ pub unsafe extern "C" fn dc_create_chat_by_contact_id( eprintln!("ignoring careless call to dc_create_chat_by_contact_id()"); return 0; } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { chat::create_by_contact_id(&ctx, contact_id) .await - .log_err(ffi_context, "Failed to create chat from contact_id") + .log_err(ctx, "Failed to create chat from contact_id") .map(|id| id.to_u32()) .unwrap_or(0) }) - .unwrap_or(0) } #[no_mangle] @@ -824,16 +617,15 @@ pub unsafe extern "C" fn dc_get_chat_id_by_contact_id( eprintln!("ignoring careless call to dc_get_chat_id_by_contact_id()"); return 0; } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { chat::get_by_contact_id(&ctx, contact_id) .await - .log_err(ffi_context, "Failed to get chat for contact_id") + .log_err(ctx, "Failed to get chat for contact_id") .map(|id| id.to_u32()) .unwrap_or(0) }) - .unwrap_or(0) } #[no_mangle] @@ -846,16 +638,15 @@ pub unsafe extern "C" fn dc_prepare_msg( eprintln!("ignoring careless call to dc_prepare_msg()"); return 0; } - let ffi_context = &mut *context; + let ctx = &mut *context; let ffi_msg: &mut MessageWrapper = &mut *msg; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { chat::prepare_msg(&ctx, ChatId::new(chat_id), &mut ffi_msg.message) .await .unwrap_or_log_default(&ctx, "Failed to prepare message") }) - .map(|msg_id| msg_id.to_u32()) - .unwrap_or(0) + .to_u32() } #[no_mangle] @@ -868,16 +659,15 @@ pub unsafe extern "C" fn dc_send_msg( eprintln!("ignoring careless call to dc_send_msg()"); return 0; } - let ffi_context = &mut *context; + let ctx = &mut *context; let ffi_msg = &mut *msg; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { chat::send_msg(&ctx, ChatId::new(chat_id), &mut ffi_msg.message) .await .unwrap_or_log_default(&ctx, "Failed to send message") }) - .map(|msg_id| msg_id.to_u32()) - .unwrap_or(0) + .to_u32() } #[no_mangle] @@ -890,16 +680,15 @@ pub unsafe extern "C" fn dc_send_text_msg( eprintln!("ignoring careless call to dc_send_text_msg()"); return 0; } - let ffi_context = &*context; + let ctx = &*context; let text_to_send = to_string_lossy(text_to_send); - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { chat::send_text_msg(&ctx, ChatId::new(chat_id), text_to_send) .await .map(|msg_id| msg_id.to_u32()) .unwrap_or_log_default(&ctx, "Failed to send text message") }) - .unwrap_or(0) } #[no_mangle] @@ -912,7 +701,7 @@ pub unsafe extern "C" fn dc_set_draft( eprintln!("ignoring careless call to dc_set_draft()"); return; } - let ffi_context = &*context; + let ctx = &*context; let msg = if msg.is_null() { None } else { @@ -920,7 +709,7 @@ pub unsafe extern "C" fn dc_set_draft( Some(&mut ffi_msg.message) }; - with_inner_async!(ffi_context, ctx, ChatId::new(chat_id).set_draft(&ctx, msg)).unwrap_or(()) + block_on(ChatId::new(chat_id).set_draft(&ctx, msg)) } #[no_mangle] @@ -933,7 +722,7 @@ pub unsafe extern "C" fn dc_add_device_msg( eprintln!("ignoring careless call to dc_add_device_msg()"); return 0; } - let ffi_context = &mut *context; + let ctx = &mut *context; let msg = if msg.is_null() { None } else { @@ -941,7 +730,7 @@ pub unsafe extern "C" fn dc_add_device_msg( Some(&mut ffi_msg.message) }; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { chat::add_device_msg( &ctx, to_opt_string_lossy(label).as_ref().map(|x| x.as_str()), @@ -950,8 +739,7 @@ pub unsafe extern "C" fn dc_add_device_msg( .await .unwrap_or_log_default(&ctx, "Failed to add device message") }) - .map(|msg_id| msg_id.to_u32()) - .unwrap_or(0) + .to_u32() } #[no_mangle] @@ -960,14 +748,13 @@ pub unsafe extern "C" fn dc_update_device_chats(context: *mut dc_context_t) { eprintln!("ignoring careless call to dc_update_device_chats()"); return; } - let ffi_context = &mut *context; + let ctx = &mut *context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { ctx.update_device_chats() .await .unwrap_or_log_default(&ctx, "Failed to add device message") }) - .unwrap_or(()) } #[no_mangle] @@ -979,14 +766,13 @@ pub unsafe extern "C" fn dc_was_device_msg_ever_added( eprintln!("ignoring careless call to dc_was_device_msg_ever_added()"); return 0; } - let ffi_context = &mut *context; + let ctx = &mut *context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { chat::was_device_msg_ever_added(&ctx, &to_string_lossy(label)) .await .unwrap_or(false) as libc::c_int }) - .unwrap_or(0) } #[no_mangle] @@ -995,9 +781,9 @@ pub unsafe extern "C" fn dc_get_draft(context: *mut dc_context_t, chat_id: u32) eprintln!("ignoring careless call to dc_get_draft()"); return ptr::null_mut(); // NULL explicitly defined as "no draft" } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { match ChatId::new(chat_id).get_draft(&ctx).await { Ok(Some(draft)) => { let ffi_msg = MessageWrapper { @@ -1013,7 +799,6 @@ pub unsafe extern "C" fn dc_get_draft(context: *mut dc_context_t, chat_id: u32) } } }) - .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -1027,14 +812,14 @@ pub unsafe extern "C" fn dc_get_chat_msgs( eprintln!("ignoring careless call to dc_get_chat_msgs()"); return ptr::null_mut(); } - let ffi_context = &*context; + let ctx = &*context; let marker_flag = if marker1before <= DC_MSG_ID_LAST_SPECIAL { None } else { Some(MsgId::new(marker1before)) }; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { let arr = dc_array_t::from( chat::get_chat_msgs(&ctx, ChatId::new(chat_id), flags, marker_flag) .await @@ -1044,7 +829,6 @@ pub unsafe extern "C" fn dc_get_chat_msgs( ); Box::into_raw(Box::new(arr)) }) - .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -1053,12 +837,9 @@ pub unsafe extern "C" fn dc_get_msg_cnt(context: *mut dc_context_t, chat_id: u32 eprintln!("ignoring careless call to dc_get_msg_cnt()"); return 0; } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { - ChatId::new(chat_id).get_msg_cnt(&ctx).await as libc::c_int - }) - .unwrap_or(0) + block_on(async move { ChatId::new(chat_id).get_msg_cnt(&ctx).await as libc::c_int }) } #[no_mangle] @@ -1070,12 +851,9 @@ pub unsafe extern "C" fn dc_get_fresh_msg_cnt( eprintln!("ignoring careless call to dc_get_fresh_msg_cnt()"); return 0; } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { - ChatId::new(chat_id).get_fresh_msg_cnt(&ctx).await as libc::c_int - }) - .unwrap_or(0) + block_on(async move { ChatId::new(chat_id).get_fresh_msg_cnt(&ctx).await as libc::c_int }) } #[no_mangle] @@ -1088,13 +866,12 @@ pub unsafe extern "C" fn dc_estimate_deletion_cnt( eprintln!("ignoring careless call to dc_estimate_deletion_cnt()"); return 0; } - let ffi_context = &*context; - with_inner_async!(ffi_context, ctx, async move { + let ctx = &*context; + block_on(async move { message::estimate_deletion_cnt(ctx, from_server != 0, seconds) .await .unwrap_or(0) as libc::c_int }) - .unwrap_or(0) } #[no_mangle] @@ -1105,9 +882,9 @@ pub unsafe extern "C" fn dc_get_fresh_msgs( eprintln!("ignoring careless call to dc_get_fresh_msgs()"); return ptr::null_mut(); } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { let arr = dc_array_t::from( ctx.get_fresh_msgs() .await @@ -1117,7 +894,6 @@ pub unsafe extern "C" fn dc_get_fresh_msgs( ); Box::into_raw(Box::new(arr)) }) - .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -1126,15 +902,14 @@ pub unsafe extern "C" fn dc_marknoticed_chat(context: *mut dc_context_t, chat_id eprintln!("ignoring careless call to dc_marknoticed_chat()"); return; } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { chat::marknoticed_chat(&ctx, ChatId::new(chat_id)) .await - .log_err(ffi_context, "Failed marknoticed chat") + .log_err(ctx, "Failed marknoticed chat") .unwrap_or(()) }) - .unwrap_or(()) } #[no_mangle] @@ -1143,15 +918,14 @@ pub unsafe extern "C" fn dc_marknoticed_all_chats(context: *mut dc_context_t) { eprintln!("ignoring careless call to dc_marknoticed_all_chats()"); return; } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { chat::marknoticed_all_chats(&ctx) .await - .log_err(ffi_context, "Failed marknoticed all chats") + .log_err(ctx, "Failed marknoticed all chats") .unwrap_or(()) }) - .unwrap_or(()) } fn from_prim(s: S) -> Option @@ -1174,14 +948,14 @@ pub unsafe extern "C" fn dc_get_chat_media( eprintln!("ignoring careless call to dc_get_chat_media()"); return ptr::null_mut(); } - let ffi_context = &*context; + let ctx = &*context; let msg_type = from_prim(msg_type).expect(&format!("invalid msg_type = {}", msg_type)); let or_msg_type2 = from_prim(or_msg_type2).expect(&format!("incorrect or_msg_type2 = {}", or_msg_type2)); let or_msg_type3 = from_prim(or_msg_type3).expect(&format!("incorrect or_msg_type3 = {}", or_msg_type3)); - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { let arr = dc_array_t::from( chat::get_chat_media( &ctx, @@ -1197,7 +971,6 @@ pub unsafe extern "C" fn dc_get_chat_media( ); Box::into_raw(Box::new(arr)) }) - .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -1219,14 +992,14 @@ pub unsafe extern "C" fn dc_get_next_media( chat::Direction::Forward }; - let ffi_context = &*context; + let ctx = &*context; let msg_type = from_prim(msg_type).expect(&format!("invalid msg_type = {}", msg_type)); let or_msg_type2 = from_prim(or_msg_type2).expect(&format!("incorrect or_msg_type2 = {}", or_msg_type2)); let or_msg_type3 = from_prim(or_msg_type3).expect(&format!("incorrect or_msg_type3 = {}", or_msg_type3)); - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { chat::get_next_media( &ctx, MsgId::new(msg_id), @@ -1239,7 +1012,6 @@ pub unsafe extern "C" fn dc_get_next_media( .map(|msg_id| msg_id.to_u32()) .unwrap_or(0) }) - .unwrap_or(0) } #[no_mangle] @@ -1252,27 +1024,27 @@ pub unsafe extern "C" fn dc_set_chat_visibility( eprintln!("ignoring careless call to dc_set_chat_visibility()"); return; } - let ffi_context = &*context; + let ctx = &*context; let visibility = match archive { 0 => ChatVisibility::Normal, 1 => ChatVisibility::Archived, 2 => ChatVisibility::Pinned, _ => { - ffi_context.warning( + warn!( + ctx, "ignoring careless call to dc_set_chat_visibility(): unknown archived state", ); return; } }; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { ChatId::new(chat_id) .set_visibility(&ctx, visibility) .await - .log_err(ffi_context, "Failed setting chat visibility") + .log_err(ctx, "Failed setting chat visibility") .unwrap_or(()) }) - .unwrap_or(()) } #[no_mangle] @@ -1281,16 +1053,15 @@ pub unsafe extern "C" fn dc_delete_chat(context: *mut dc_context_t, chat_id: u32 eprintln!("ignoring careless call to dc_delete_chat()"); return; } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { ChatId::new(chat_id) .delete(&ctx) .await - .log_err(ffi_context, "Failed chat delete") + .log_err(ctx, "Failed chat delete") .unwrap_or(()) }) - .unwrap_or(()) } #[no_mangle] @@ -1302,13 +1073,12 @@ pub unsafe extern "C" fn dc_get_chat_contacts( eprintln!("ignoring careless call to dc_get_chat_contacts()"); return ptr::null_mut(); } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { let arr = dc_array_t::from(chat::get_chat_contacts(&ctx, ChatId::new(chat_id)).await); Box::into_raw(Box::new(arr)) }) - .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -1321,9 +1091,9 @@ pub unsafe extern "C" fn dc_search_msgs( eprintln!("ignoring careless call to dc_search_msgs()"); return ptr::null_mut(); } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { let arr = dc_array_t::from( ctx.search_msgs(ChatId::new(chat_id), to_string_lossy(query)) .await @@ -1333,7 +1103,6 @@ pub unsafe extern "C" fn dc_search_msgs( ); Box::into_raw(Box::new(arr)) }) - .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -1342,9 +1111,9 @@ pub unsafe extern "C" fn dc_get_chat(context: *mut dc_context_t, chat_id: u32) - eprintln!("ignoring careless call to dc_get_chat()"); return ptr::null_mut(); } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { match chat::Chat::load_from_db(&ctx, ChatId::new(chat_id)).await { Ok(chat) => { let ffi_chat = ChatWrapper { context, chat }; @@ -1353,7 +1122,6 @@ pub unsafe extern "C" fn dc_get_chat(context: *mut dc_context_t, chat_id: u32) - Err(_) => ptr::null_mut(), } }) - .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -1366,21 +1134,20 @@ pub unsafe extern "C" fn dc_create_group_chat( eprintln!("ignoring careless call to dc_create_group_chat()"); return 0; } - let ffi_context = &*context; + let ctx = &*context; let verified = if let Some(s) = contact::VerifiedStatus::from_i32(verified) { s } else { return 0; }; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { chat::create_group_chat(&ctx, verified, to_string_lossy(name)) .await - .log_err(ffi_context, "Failed to create group chat") + .log_err(ctx, "Failed to create group chat") .map(|id| id.to_u32()) .unwrap_or(0) }) - .unwrap_or(0) } #[no_mangle] @@ -1393,15 +1160,10 @@ pub unsafe extern "C" fn dc_is_contact_in_chat( eprintln!("ignoring careless call to dc_is_contact_in_chat()"); return 0; } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!( - ffi_context, - ctx, - chat::is_contact_in_chat(&ctx, ChatId::new(chat_id), contact_id) - ) - .unwrap_or_default() - .into() + block_on(async move { chat::is_contact_in_chat(&ctx, ChatId::new(chat_id), contact_id).await }) + .into() } #[no_mangle] @@ -1414,12 +1176,11 @@ pub unsafe extern "C" fn dc_add_contact_to_chat( eprintln!("ignoring careless call to dc_add_contact_to_chat()"); return 0; } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { chat::add_contact_to_chat(&ctx, ChatId::new(chat_id), contact_id).await as libc::c_int }) - .unwrap_or(0) } #[no_mangle] @@ -1432,15 +1193,14 @@ pub unsafe extern "C" fn dc_remove_contact_from_chat( eprintln!("ignoring careless call to dc_remove_contact_from_chat()"); return 0; } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { chat::remove_contact_from_chat(&ctx, ChatId::new(chat_id), contact_id) .await .map(|_| 1) .unwrap_or_log_default(&ctx, "Failed to remove contact") }) - .unwrap_or(0) } #[no_mangle] @@ -1453,15 +1213,14 @@ pub unsafe extern "C" fn dc_set_chat_name( eprintln!("ignoring careless call to dc_set_chat_name()"); return 0; } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { chat::set_chat_name(&ctx, ChatId::new(chat_id), to_string_lossy(name)) .await .map(|_| 1) .unwrap_or_log_default(&ctx, "Failed to set chat name") }) - .unwrap_or(0) } #[no_mangle] @@ -1474,15 +1233,14 @@ pub unsafe extern "C" fn dc_set_chat_profile_image( eprintln!("ignoring careless call to dc_set_chat_profile_image()"); return 0; } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { chat::set_chat_profile_image(&ctx, ChatId::new(chat_id), to_string_lossy(image)) .await .map(|_| 1) .unwrap_or_log_default(&ctx, "Failed to set profile image") }) - .unwrap_or(0) } #[no_mangle] @@ -1495,26 +1253,26 @@ pub unsafe extern "C" fn dc_set_chat_mute_duration( eprintln!("ignoring careless call to dc_set_chat_mute_duration()"); return 0; } - let ffi_context = &*context; + let ctx = &*context; let muteDuration = match duration { 0 => MuteDuration::NotMuted, -1 => MuteDuration::Forever, n if n > 0 => MuteDuration::Until(SystemTime::now() + Duration::from_secs(duration as u64)), _ => { - ffi_context.warning( + warn!( + ctx, "dc_chat_set_mute_duration(): Can not use negative duration other than -1", ); return 0; } }; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { chat::set_muted(&ctx, ChatId::new(chat_id), muteDuration) .await .map(|_| 1) .unwrap_or_log_default(&ctx, "Failed to set mute duration") }) - .unwrap_or(0) } #[no_mangle] @@ -1526,15 +1284,9 @@ pub unsafe extern "C" fn dc_get_msg_info( eprintln!("ignoring careless call to dc_get_msg_info()"); return "".strdup(); } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!( - ffi_context, - ctx, - message::get_msg_info(&ctx, MsgId::new(msg_id)) - ) - .map(|s| s.strdup()) - .unwrap_or_else(|_| ptr::null_mut()) + block_on(message::get_msg_info(&ctx, MsgId::new(msg_id))).strdup() } #[no_mangle] @@ -1546,15 +1298,14 @@ pub unsafe extern "C" fn dc_get_mime_headers( eprintln!("ignoring careless call to dc_get_mime_headers()"); return ptr::null_mut(); // NULL explicitly defined as "no mime headers" } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { message::get_mime_headers(&ctx, MsgId::new(msg_id)) .await .map(|s| s.strdup()) .unwrap_or_else(ptr::null_mut) }) - .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -1567,10 +1318,10 @@ pub unsafe extern "C" fn dc_delete_msgs( eprintln!("ignoring careless call to dc_delete_msgs()"); return; } - let ffi_context = &*context; + let ctx = &*context; let msg_ids = convert_and_prune_message_ids(msg_ids, msg_cnt); - with_inner_async!(ffi_context, ctx, message::delete_msgs(&ctx, &msg_ids)).unwrap_or(()) + block_on(message::delete_msgs(&ctx, &msg_ids)) } #[no_mangle] @@ -1579,9 +1330,9 @@ pub unsafe extern "C" fn dc_empty_server(context: *mut dc_context_t, flags: u32) eprintln!("ignoring careless call to dc_empty_server()"); return; } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, message::dc_empty_server(&ctx, flags)).unwrap_or(()) + block_on(message::dc_empty_server(&ctx, flags)) } #[no_mangle] @@ -1600,14 +1351,13 @@ pub unsafe extern "C" fn dc_forward_msgs( return; } let msg_ids = convert_and_prune_message_ids(msg_ids, msg_cnt); - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { chat::forward_msgs(&ctx, &msg_ids[..], ChatId::new(chat_id)) .await .unwrap_or_log_default(&ctx, "Failed to forward message") }) - .unwrap_or_default() } #[no_mangle] @@ -1616,9 +1366,9 @@ pub unsafe extern "C" fn dc_marknoticed_contact(context: *mut dc_context_t, cont eprintln!("ignoring careless call to dc_marknoticed_contact()"); return; } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, Contact::mark_noticed(&ctx, contact_id)).unwrap_or(()) + block_on(Contact::mark_noticed(&ctx, contact_id)) } #[no_mangle] @@ -1632,9 +1382,9 @@ pub unsafe extern "C" fn dc_markseen_msgs( return; } let msg_ids = convert_and_prune_message_ids(msg_ids, msg_cnt); - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, message::markseen_msgs(&ctx, msg_ids)).ok(); + block_on(message::markseen_msgs(&ctx, msg_ids)); } #[no_mangle] @@ -1649,14 +1399,9 @@ pub unsafe extern "C" fn dc_star_msgs( return; } let msg_ids = convert_and_prune_message_ids(msg_ids, msg_cnt); - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!( - ffi_context, - ctx, - message::star_msgs(&ctx, msg_ids, star == 1) - ) - .ok(); + block_on(message::star_msgs(&ctx, msg_ids, star == 1)); } #[no_mangle] @@ -1665,9 +1410,9 @@ pub unsafe extern "C" fn dc_get_msg(context: *mut dc_context_t, msg_id: u32) -> eprintln!("ignoring careless call to dc_get_msg()"); return ptr::null_mut(); } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { let message = match message::Message::load_from_db(&ctx, MsgId::new(msg_id)).await { Ok(msg) => msg, Err(e) => { @@ -1690,7 +1435,6 @@ pub unsafe extern "C" fn dc_get_msg(context: *mut dc_context_t, msg_id: u32) -> let ffi_msg = MessageWrapper { context, message }; Box::into_raw(Box::new(ffi_msg)) }) - .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -1712,14 +1456,13 @@ pub unsafe extern "C" fn dc_lookup_contact_id_by_addr( eprintln!("ignoring careless call to dc_lookup_contact_id_by_addr()"); return 0; } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!( - ffi_context, - ctx, - Contact::lookup_id_by_addr(&ctx, to_string_lossy(addr), Origin::IncomingReplyTo) - ) - .unwrap_or(0) + block_on(Contact::lookup_id_by_addr( + &ctx, + to_string_lossy(addr), + Origin::IncomingReplyTo, + )) } #[no_mangle] @@ -1732,16 +1475,15 @@ pub unsafe extern "C" fn dc_create_contact( eprintln!("ignoring careless call to dc_create_contact()"); return 0; } - let ffi_context = &*context; + let ctx = &*context; let name = to_string_lossy(name); - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { match Contact::create(&ctx, name, to_string_lossy(addr)).await { Ok(id) => id, Err(_) => 0, } }) - .unwrap_or(0) } #[no_mangle] @@ -1753,15 +1495,14 @@ pub unsafe extern "C" fn dc_add_address_book( eprintln!("ignoring careless call to dc_add_address_book()"); return 0; } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { match Contact::add_address_book(&ctx, to_string_lossy(addr_book)).await { Ok(cnt) => cnt as libc::c_int, Err(_) => 0, } }) - .unwrap_or(0) } #[no_mangle] @@ -1774,16 +1515,15 @@ pub unsafe extern "C" fn dc_get_contacts( eprintln!("ignoring careless call to dc_get_contacts()"); return ptr::null_mut(); } - let ffi_context = &*context; + let ctx = &*context; let query = to_opt_string_lossy(query); - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { match Contact::get_all(&ctx, flags, query).await { Ok(contacts) => Box::into_raw(Box::new(dc_array_t::from(contacts))), Err(_) => ptr::null_mut(), } }) - .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -1792,9 +1532,9 @@ pub unsafe extern "C" fn dc_get_blocked_cnt(context: *mut dc_context_t) -> libc: eprintln!("ignoring careless call to dc_get_blocked_cnt()"); return 0; } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, Contact::get_blocked_cnt(&ctx)).unwrap_or(0) as libc::c_int + block_on(Contact::get_blocked_cnt(&ctx)) as libc::c_int } #[no_mangle] @@ -1805,14 +1545,13 @@ pub unsafe extern "C" fn dc_get_blocked_contacts( eprintln!("ignoring careless call to dc_get_blocked_contacts()"); return ptr::null_mut(); } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { Box::into_raw(Box::new(dc_array_t::from( Contact::get_all_blocked(&ctx).await, ))) }) - .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -1825,15 +1564,14 @@ pub unsafe extern "C" fn dc_block_contact( eprintln!("ignoring careless call to dc_block_contact()"); return; } - let ffi_context = &*context; - with_inner_async!(ffi_context, ctx, async move { + let ctx = &*context; + block_on(async move { if block == 0 { Contact::unblock(&ctx, contact_id).await; } else { Contact::block(&ctx, contact_id).await; } - }) - .ok(); + }); } #[no_mangle] @@ -1845,9 +1583,9 @@ pub unsafe extern "C" fn dc_get_contact_encrinfo( eprintln!("ignoring careless call to dc_get_contact_encrinfo()"); return "".strdup(); } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { Contact::get_encrinfo(&ctx, contact_id) .await .map(|s| s.strdup()) @@ -1856,7 +1594,6 @@ pub unsafe extern "C" fn dc_get_contact_encrinfo( ptr::null_mut() }) }) - .unwrap_or_else(|_| "".strdup()) } #[no_mangle] @@ -1868,15 +1605,14 @@ pub unsafe extern "C" fn dc_delete_contact( eprintln!("ignoring careless call to dc_delete_contact()"); return 0; } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { match Contact::delete(&ctx, contact_id).await { Ok(_) => 1, Err(_) => 0, } }) - .unwrap_or(0) } #[no_mangle] @@ -1888,15 +1624,14 @@ pub unsafe extern "C" fn dc_get_contact( eprintln!("ignoring careless call to dc_get_contact()"); return ptr::null_mut(); } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { Contact::get_by_id(&ctx, contact_id) .await .map(|contact| Box::into_raw(Box::new(ContactWrapper { context, contact }))) .unwrap_or_else(|_| ptr::null_mut()) }) - .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -1918,16 +1653,15 @@ pub unsafe extern "C" fn dc_imex( } }; - let ffi_context = &*context; + let ctx = &*context; let param1 = to_opt_string_lossy(param1); - with_inner_spawn!(ffi_context, ctx, async move { + spawn(async move { imex::imex(&ctx, what, param1) .await - .log_err(ffi_context, "IMEX failed") - }) - .ok(); + .log_err(ctx, "IMEX failed") + }); } #[no_mangle] @@ -1939,9 +1673,9 @@ pub unsafe extern "C" fn dc_imex_has_backup( eprintln!("ignoring careless call to dc_imex_has_backup()"); return ptr::null_mut(); // NULL explicitly defined as "has no backup" } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { match imex::has_backup(&ctx, to_string_lossy(dir)).await { Ok(res) => res.strdup(), Err(err) => { @@ -1952,7 +1686,6 @@ pub unsafe extern "C" fn dc_imex_has_backup( } } }) - .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -1961,9 +1694,9 @@ pub unsafe extern "C" fn dc_initiate_key_transfer(context: *mut dc_context_t) -> eprintln!("ignoring careless call to dc_initiate_key_transfer()"); return ptr::null_mut(); // NULL explicitly defined as "error" } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { match imex::initiate_key_transfer(&ctx).await { Ok(res) => res.strdup(), Err(err) => { @@ -1972,7 +1705,6 @@ pub unsafe extern "C" fn dc_initiate_key_transfer(context: *mut dc_context_t) -> } } }) - .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -1988,9 +1720,9 @@ pub unsafe extern "C" fn dc_continue_key_transfer( eprintln!("ignoring careless call to dc_continue_key_transfer()"); return 0; } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { match imex::continue_key_transfer(&ctx, MsgId::new(msg_id), &to_string_lossy(setup_code)) .await { @@ -2001,7 +1733,6 @@ pub unsafe extern "C" fn dc_continue_key_transfer( } } }) - .unwrap_or(0) } #[no_mangle] @@ -2010,8 +1741,8 @@ pub unsafe extern "C" fn dc_stop_ongoing_process(context: *mut dc_context_t) { eprintln!("ignoring careless call to dc_stop_ongoing_process()"); return; } - let ffi_context = &*context; - with_inner_async!(ffi_context, ctx, ctx.stop_ongoing()).ok(); + let ctx = &*context; + block_on(ctx.stop_ongoing()); } #[no_mangle] @@ -2023,13 +1754,12 @@ pub unsafe extern "C" fn dc_check_qr( eprintln!("ignoring careless call to dc_check_qr()"); return ptr::null_mut(); } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { let lot = qr::check_qr(&ctx, to_string_lossy(qr)).await; Box::into_raw(Box::new(lot)) }) - .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -2041,15 +1771,14 @@ pub unsafe extern "C" fn dc_get_securejoin_qr( eprintln!("ignoring careless call to dc_get_securejoin_qr()"); return "".strdup(); } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { securejoin::dc_get_securejoin_qr(&ctx, ChatId::new(chat_id)) .await .unwrap_or_else(|| "".to_string()) .strdup() }) - .unwrap_or_else(|_| "".strdup()) } #[no_mangle] @@ -2061,13 +1790,10 @@ pub unsafe extern "C" fn dc_join_securejoin( eprintln!("ignoring careless call to dc_join_securejoin()"); return 0; } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { - securejoin::dc_join_securejoin(&ctx, &to_string_lossy(qr)).await - }) - .map(|v| v.to_u32()) - .unwrap_or(0) + block_on(async move { securejoin::dc_join_securejoin(&ctx, &to_string_lossy(qr)).await }) + .to_u32() } #[no_mangle] @@ -2080,12 +1806,9 @@ pub unsafe extern "C" fn dc_send_locations_to_chat( eprintln!("ignoring careless call to dc_send_locations_to_chat()"); return; } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, { - location::send_locations_to_chat(&ctx, ChatId::new(chat_id), seconds as i64) - }) - .ok(); + block_on({ location::send_locations_to_chat(&ctx, ChatId::new(chat_id), seconds as i64) }); } #[no_mangle] @@ -2097,14 +1820,12 @@ pub unsafe extern "C" fn dc_is_sending_locations_to_chat( eprintln!("ignoring careless call to dc_is_sending_locations_to_chat()"); return 0; } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!( - ffi_context, - ctx, - location::is_sending_locations_to_chat(&ctx, ChatId::new(chat_id)) - ) - .unwrap_or_default() as libc::c_int + block_on(location::is_sending_locations_to_chat( + &ctx, + ChatId::new(chat_id), + )) as libc::c_int } #[no_mangle] @@ -2118,14 +1839,9 @@ pub unsafe extern "C" fn dc_set_location( eprintln!("ignoring careless call to dc_set_location()"); return 0; } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!( - ffi_context, - ctx, - location::set(&ctx, latitude, longitude, accuracy) - ) - .unwrap_or(false) as _ + block_on(location::set(&ctx, latitude, longitude, accuracy)) as _ } #[no_mangle] @@ -2140,9 +1856,9 @@ pub unsafe extern "C" fn dc_get_locations( eprintln!("ignoring careless call to dc_get_locations()"); return ptr::null_mut(); } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { let res = location::get_range( &ctx, ChatId::new(chat_id), @@ -2153,7 +1869,6 @@ pub unsafe extern "C" fn dc_get_locations( .await; Box::into_raw(Box::new(dc_array_t::from(res))) }) - .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -2162,14 +1877,14 @@ pub unsafe extern "C" fn dc_delete_all_locations(context: *mut dc_context_t) { eprintln!("ignoring careless call to dc_delete_all_locations()"); return; } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { location::delete_all(&ctx) .await - .log_err(ffi_context, "Failed to delete locations") - }) - .ok(); + .log_err(ctx, "Failed to delete locations") + .ok() + }); } // dc_array_t @@ -2436,16 +2151,15 @@ pub unsafe extern "C" fn dc_chatlist_get_summary( Some(&ffi_chat.chat) }; let ffi_list = &*chatlist; - let ffi_context: &ContextWrapper = &*ffi_list.context; + let ctx = &*ffi_list.context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { let lot = ffi_list .list .get_summary(&ctx, index as usize, maybe_chat) .await; Box::into_raw(Box::new(lot)) }) - .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -2524,15 +2238,14 @@ pub unsafe extern "C" fn dc_chat_get_profile_image(chat: *mut dc_chat_t) -> *mut return ptr::null_mut(); // NULL explicitly defined as "no image" } let ffi_chat = &*chat; - let ffi_context = &*ffi_chat.context; + let ctx = &*ffi_chat.context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { match ffi_chat.chat.get_profile_image(&ctx).await { Some(p) => p.to_string_lossy().strdup(), None => ptr::null_mut(), } }) - .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -2542,9 +2255,9 @@ pub unsafe extern "C" fn dc_chat_get_color(chat: *mut dc_chat_t) -> u32 { return 0; } let ffi_chat = &*chat; - let ffi_context = &*ffi_chat.context; + let ctx = &*ffi_chat.context; - with_inner_async!(ffi_context, ctx, ffi_chat.chat.get_color(&ctx)).unwrap_or(0) + block_on(ffi_chat.chat.get_color(&ctx)) } #[no_mangle] @@ -2661,9 +2374,9 @@ pub unsafe extern "C" fn dc_chat_get_info_json( eprintln!("ignoring careless call to dc_chat_get_info_json()"); return "".strdup(); } - let ffi_context = &*context; + let ctx = &*context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { let chat = match chat::Chat::load_from_db(&ctx, ChatId::new(chat_id)).await { Ok(chat) => chat, Err(err) => { @@ -2685,7 +2398,6 @@ pub unsafe extern "C" fn dc_chat_get_info_json( .unwrap_or_log_default(&ctx, "dc_get_chat_info_json() failed to serialise to json") .strdup() }) - .unwrap_or_else(|_| "".strdup()) } // dc_msg_t @@ -2834,16 +2546,12 @@ pub unsafe extern "C" fn dc_msg_get_file(msg: *mut dc_msg_t) -> *mut libc::c_cha return "".strdup(); } let ffi_msg = &*msg; - let ffi_context = &*ffi_msg.context; - ffi_context - .with_inner(|ctx| { - ffi_msg - .message - .get_file(ctx) - .map(|p| p.to_string_lossy().strdup()) - .unwrap_or_else(|| "".strdup()) - }) - .unwrap_or_else(|_| "".strdup()) + let ctx = &*ffi_msg.context; + ffi_msg + .message + .get_file(ctx) + .map(|p| p.to_string_lossy().strdup()) + .unwrap_or_else(|| "".strdup()) } #[no_mangle] @@ -2877,9 +2585,9 @@ pub unsafe extern "C" fn dc_msg_get_filebytes(msg: *mut dc_msg_t) -> u64 { return 0; } let ffi_msg = &*msg; - let ffi_context = &*ffi_msg.context; + let ctx = &*ffi_msg.context; - with_inner_async!(ffi_context, ctx, ffi_msg.message.get_filebytes(&ctx)).unwrap_or(0) + block_on(ffi_msg.message.get_filebytes(&ctx)) } #[no_mangle] @@ -2938,13 +2646,12 @@ pub unsafe extern "C" fn dc_msg_get_summary( Some(&ffi_chat.chat) }; let ffi_msg = &mut *msg; - let ffi_context = &*ffi_msg.context; + let ctx = &*ffi_msg.context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { let lot = ffi_msg.message.get_summary(&ctx, maybe_chat).await; Box::into_raw(Box::new(lot)) }) - .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -2957,14 +2664,13 @@ pub unsafe extern "C" fn dc_msg_get_summarytext( return "".strdup(); } let ffi_msg = &mut *msg; - let ffi_context = &*ffi_msg.context; + let ctx = &*ffi_msg.context; - with_inner_async!(ffi_context, ctx, { + block_on({ ffi_msg .message .get_summarytext(&ctx, approx_characters.try_into().unwrap_or_default()) }) - .unwrap_or_default() .strdup() } @@ -3055,10 +2761,9 @@ pub unsafe extern "C" fn dc_msg_get_setupcodebegin(msg: *mut dc_msg_t) -> *mut l return "".strdup(); } let ffi_msg = &*msg; - let ffi_context = &*ffi_msg.context; + let ctx = &*ffi_msg.context; - with_inner_async!(ffi_context, ctx, ffi_msg.message.get_setupcodebegin(&ctx)) - .map(|s| s.unwrap_or_default()) + block_on(ffi_msg.message.get_setupcodebegin(&ctx)) .unwrap_or_default() .strdup() } @@ -3140,14 +2845,13 @@ pub unsafe extern "C" fn dc_msg_latefiling_mediasize( return; } let ffi_msg = &mut *msg; - let ffi_context = &*ffi_msg.context; + let ctx = &*ffi_msg.context; - with_inner_async!(ffi_context, ctx, { + block_on({ ffi_msg .message .latefiling_mediasize(&ctx, width, height, duration) - }) - .ok(); + }); } // dc_contact_t @@ -3251,9 +2955,9 @@ pub unsafe extern "C" fn dc_contact_get_profile_image( return ptr::null_mut(); // NULL explicitly defined as "no profile image" } let ffi_contact = &*contact; - let ffi_context = &*ffi_contact.context; + let ctx = &*ffi_contact.context; - with_inner_async!(ffi_context, ctx, async move { + block_on(async move { ffi_contact .contact .get_profile_image(&ctx) @@ -3261,7 +2965,6 @@ pub unsafe extern "C" fn dc_contact_get_profile_image( .map(|p| p.to_string_lossy().strdup()) .unwrap_or_else(std::ptr::null_mut) }) - .unwrap_or_else(|_| ptr::null_mut()) } #[no_mangle] @@ -3291,12 +2994,9 @@ pub unsafe extern "C" fn dc_contact_is_verified(contact: *mut dc_contact_t) -> l return 0; } let ffi_contact = &*contact; - let ffi_context = &*ffi_contact.context; + let ctx = &*ffi_contact.context; - with_inner_async!(ffi_context, ctx, async move { - ffi_contact.contact.is_verified(&ctx).await as libc::c_int - }) - .unwrap_or(0) + block_on(async move { ffi_contact.contact.is_verified(&ctx).await as libc::c_int }) } // dc_lot_t @@ -3394,7 +3094,7 @@ trait ResultExt { /// /// You can do this as soon as the wrapper exists, it does not /// have to be open (which is required for the `warn!()` macro). - fn log_err(self, wrapper: &ContextWrapper, message: &str) -> Result; + fn log_err(self, wrapper: &Context, message: &str) -> Result; } impl ResultExt for Result { @@ -3408,11 +3108,9 @@ impl ResultExt for Result { } } - fn log_err(self, wrapper: &ContextWrapper, message: &str) -> Result { + fn log_err(self, ctx: &Context, message: &str) -> Result { self.map_err(|err| { - unsafe { - wrapper.warning(&format!("{}: {}", message, err)); - } + warn!(ctx, "{}: {}", message, err); err }) } From 8569e1c18b0d46f786490ce57e0cd8e2dedf61b5 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Fri, 22 May 2020 23:56:03 +0200 Subject: [PATCH 091/118] python: first pass at updates for dc_open/dc_close removal --- python/src/deltachat/account.py | 22 +++++++++---------- python/tests/test_lowlevel.py | 39 +++++++-------------------------- 2 files changed, 18 insertions(+), 43 deletions(-) diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index 3925d3292..c5bfe4109 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -42,21 +42,20 @@ class Account(object): self.add_account_plugin(self) + self.db_path = db_path + if hasattr(db_path, "encode"): + db_path = db_path.encode("utf8") + self._dc_context = ffi.gc( - lib.dc_context_new(ffi.NULL, as_dc_charpointer(os_name)), + lib.dc_context_new(as_dc_charpointer(os_name), db_path, ffi.NULL), _destroy_dc_context, ) + if self._dc_context == ffi.NULL: + raise ValueError("Could not dc_context_new: {} {}".format(os_name, db_path)) hook = hookspec.Global._get_plugin_manager().hook self._shutdown_event = Event() - - # open database - self.db_path = db_path - if hasattr(db_path, "encode"): - db_path = db_path.encode("utf8") - if not lib.dc_open(self._dc_context, db_path, ffi.NULL): - raise ValueError("Could not dc_open: {}".format(db_path)) self._event_thread = EventThread(self) self._configkeys = self.get_config("sys.config_keys").split() atexit.register(self.shutdown) @@ -622,15 +621,14 @@ class Account(object): self.stop_scheduler() - self.log("dc_close") - # the dc_close triggers get_next_event to return ffi.NULL + self.log("remove dc_context") + # the dc_context_unref triggers get_next_event to return ffi.NULL # which in turns makes the event thread finish execution - lib.dc_close(dc_context) + self._dc_context = None self.log("wait for event thread to finish") self._event_thread.wait() - self._dc_context = None atexit.unregister(self.shutdown) self._shutdown_event.set() hook = hookspec.Global._get_plugin_manager().hook diff --git a/python/tests/test_lowlevel.py b/python/tests/test_lowlevel.py index 466b0504a..b8b66b61b 100644 --- a/python/tests/test_lowlevel.py +++ b/python/tests/test_lowlevel.py @@ -10,8 +10,8 @@ from deltachat.capi import lib def test_empty_context(): - ctx = capi.lib.dc_context_new(capi.ffi.NULL, capi.ffi.NULL) - capi.lib.dc_close(ctx) + ctx = capi.lib.dc_context_new(capi.ffi.NULL, capi.ffi.NULL, capi.ffi.NULL) + capi.lib.dc_context_unref(ctx) def test_dc_close_events(tmpdir, acfactory): @@ -32,24 +32,20 @@ def test_dc_close_events(tmpdir, acfactory): def test_wrong_db(tmpdir): - dc_context = ffi.gc( - lib.dc_context_new(ffi.NULL, ffi.NULL), - lib.dc_context_unref, - ) p = tmpdir.join("hello.db") # write an invalid database file p.write("x123" * 10) - assert not lib.dc_open(dc_context, p.strpath.encode("ascii"), ffi.NULL) + assert ffi.NULL == lib.dc_context_new(ffi.NULL, ffi.NULL, p.strpath.encode("ascii"), ffi.NULL) def test_empty_blobdir(tmpdir): + db_fname = tmpdir.join("hello.db") # Apparently some client code expects this to be the same as passing NULL. ctx = ffi.gc( - lib.dc_context_new(ffi.NULL, ffi.NULL), + lib.dc_context_new(ffi.NULL, ffi.NULL, db_fname.strpath.encode("ascii"), b""), lib.dc_context_unref, ) - db_fname = tmpdir.join("hello.db") - assert lib.dc_open(ctx, db_fname.strpath.encode("ascii"), b"") + assert ctx != ffi.NULL def test_event_defines(): @@ -107,30 +103,11 @@ def test_get_info_closed(): def test_get_info_open(tmpdir): + db_fname = tmpdir.join("test.db") ctx = ffi.gc( - lib.dc_context_new(ffi.NULL, ffi.NULL), + lib.dc_context_new(ffi.NULL, ffi.NULL, db_fname.strpath.encode("ascii"), ffi.NULL), lib.dc_context_unref, ) - db_fname = tmpdir.join("test.db") - lib.dc_open(ctx, db_fname.strpath.encode("ascii"), ffi.NULL) info = cutil.from_dc_charpointer(lib.dc_get_info(ctx)) assert 'deltachat_core_version' in info assert 'database_dir' in info - - -def test_is_open_closed(): - ctx = ffi.gc( - lib.dc_context_new(ffi.NULL, ffi.NULL), - lib.dc_context_unref, - ) - assert lib.dc_is_open(ctx) == 0 - - -def test_is_open_actually_open(tmpdir): - ctx = ffi.gc( - lib.dc_context_new(ffi.NULL, ffi.NULL), - lib.dc_context_unref, - ) - db_fname = tmpdir.join("test.db") - lib.dc_open(ctx, db_fname.strpath.encode("ascii"), ffi.NULL) - assert lib.dc_is_open(ctx) == 1 From 05f79c1c01192f05aa98333a9fac13687054260e Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Sat, 23 May 2020 00:17:50 +0200 Subject: [PATCH 092/118] update dependencies --- Cargo.lock | 305 +++++++++++++++++++-------------------- Cargo.toml | 9 +- deltachat-ffi/Cargo.toml | 2 +- src/events.rs | 7 +- src/scheduler.rs | 16 +- 5 files changed, 170 insertions(+), 169 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index de71a318f..17eb26e3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -62,7 +62,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" dependencies = [ - "winapi 0.3.8", + "winapi", ] [[package]] @@ -71,7 +71,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" dependencies = [ - "winapi 0.3.8", + "winapi", ] [[package]] @@ -136,8 +136,9 @@ dependencies = [ [[package]] name = "async-imap" -version = "0.2.0" -source = "git+https://github.com/async-email/async-imap?rev=1e8fdd86dbc99b6d92ab4667b426092060fe8b16#1e8fdd86dbc99b6d92ab4667b426092060fe8b16" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dacff85e0a4ef94b196344a715bfb655d1ec806693d7a566569154b5ea74284" dependencies = [ "async-native-tls", "async-std", @@ -192,40 +193,34 @@ dependencies = [ [[package]] name = "async-std" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "538ecb01eb64eecd772087e5b6f7540cbc917f047727339a472dafed2185b267" +checksum = "a45cee2749d880d7066e328a7e161c7470ced883b2fd000ca4643e9f1dd5083a" dependencies = [ "async-attributes", "async-task", - "broadcaster", - "crossbeam-channel", - "crossbeam-deque", "crossbeam-utils", + "futures-channel", "futures-core", "futures-io", "futures-timer", "kv-log-macro", "log", "memchr", - "mio", - "mio-uds", "num_cpus", "once_cell", "pin-project-lite", "pin-utils", "slab", + "smol", + "wasm-bindgen-futures", ] [[package]] name = "async-task" -version = "1.3.1" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ac2c016b079e771204030951c366db398864f5026f84a44dafb0ff20f02085d" -dependencies = [ - "libc", - "winapi 0.3.8", -] +checksum = "c17772156ef2829aadc587461c7753af20b7e8db1529bc66855add962a3b35d3" [[package]] name = "async-trait" @@ -246,7 +241,7 @@ checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", - "winapi 0.3.8", + "winapi", ] [[package]] @@ -378,20 +373,6 @@ dependencies = [ "opaque-debug", ] -[[package]] -name = "broadcaster" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c972e21e0d055a36cf73e4daae870941fe7a8abcd5ac3396aab9e4c126bd87" -dependencies = [ - "futures-channel", - "futures-core", - "futures-sink", - "futures-util", - "parking_lot", - "slab", -] - [[package]] name = "buf_redux" version = "0.8.4" @@ -601,6 +582,20 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69323bff1fb41c635347b8ead484a5ca6c3f11914d784170b158d8449ab07f8e" +dependencies = [ + "cfg-if", + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + [[package]] name = "crossbeam-channel" version = "0.4.2" @@ -753,7 +748,6 @@ dependencies = [ "byteorder", "charset", "chrono", - "crossbeam-channel", "debug_stub_derive", "deltachat_derive", "email", @@ -880,7 +874,7 @@ checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" dependencies = [ "libc", "redox_users", - "winapi 0.3.8", + "winapi", ] [[package]] @@ -1135,22 +1129,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" -[[package]] -name = "fuchsia-zircon" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -dependencies = [ - "bitflags", - "fuchsia-zircon-sys", -] - -[[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" - [[package]] name = "futures" version = "0.1.29" @@ -1234,9 +1212,13 @@ dependencies = [ [[package]] name = "futures-timer" -version = "2.0.2" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1de7508b218029b0f01662ed8f61b1c964b3ae99d6f25462d0f55a595109df6" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" +dependencies = [ + "gloo-timers", + "send_wrapper", +] [[package]] name = "futures-util" @@ -1302,6 +1284,19 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" +[[package]] +name = "gloo-timers" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "heck" version = "0.3.1" @@ -1367,9 +1362,9 @@ dependencies = [ [[package]] name = "http-types" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05af75a78dfeb163d472b1d27bebb6a8845917a069accdf53a9bed47aaff9bfc" +checksum = "49d9f44462c2e59d5d5826e7ba74b121ee2fb0ff091ef849692694f1d77aaf50" dependencies = [ "anyhow", "async-std", @@ -1378,6 +1373,8 @@ dependencies = [ "infer", "omnom", "pin-project-lite", + "serde", + "serde_json", "url", ] @@ -1538,16 +1535,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" -[[package]] -name = "kernel32-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -dependencies = [ - "winapi 0.2.8", - "winapi-build", -] - [[package]] name = "kv-log-macro" version = "1.0.5" @@ -1739,48 +1726,6 @@ dependencies = [ "adler32", ] -[[package]] -name = "mio" -version = "0.6.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" -dependencies = [ - "cfg-if", - "fuchsia-zircon", - "fuchsia-zircon-sys", - "iovec", - "kernel32-sys", - "libc", - "log", - "miow", - "net2", - "slab", - "winapi 0.2.8", -] - -[[package]] -name = "mio-uds" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" -dependencies = [ - "iovec", - "libc", - "mio", -] - -[[package]] -name = "miow" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" -dependencies = [ - "kernel32-sys", - "net2", - "winapi 0.2.8", - "ws2_32-sys", -] - [[package]] name = "native-tls" version = "0.2.4" @@ -1799,17 +1744,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "net2" -version = "0.2.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7" -dependencies = [ - "cfg-if", - "libc", - "winapi 0.3.8", -] - [[package]] name = "nix" version = "0.13.1" @@ -1823,6 +1757,19 @@ dependencies = [ "void", ] +[[package]] +name = "nix" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" +dependencies = [ + "bitflags", + "cc", + "cfg-if", + "libc", + "void", +] + [[package]] name = "nodrop" version = "0.1.14" @@ -2016,7 +1963,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" dependencies = [ - "winapi 0.3.8", + "winapi", ] [[package]] @@ -2049,7 +1996,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "winapi 0.3.8", + "winapi", ] [[package]] @@ -2138,6 +2085,18 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b0deb65f46e873ba8aa7c6a8dbe3f23cb1bf59c339a81a1d56361dde4d66ac8" +dependencies = [ + "crossbeam-utils", + "futures-io", + "futures-sink", + "futures-util", +] + [[package]] name = "pkg-config" version = "0.3.17" @@ -2199,9 +2158,9 @@ checksum = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694" [[package]] name = "proc-macro2" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de40dd4ff82d9c9bab6dae29dbab1167e515f8df9ed17d2987cb6012db206933" +checksum = "70a50b9351bfa8d65a7d93ce712dc63d2fd15ddbf2c36990fc7cac344859c04f" dependencies = [ "unicode-xid 0.2.0", ] @@ -2302,7 +2261,7 @@ dependencies = [ "libc", "rand_core 0.3.1", "rdrand", - "winapi 0.3.8", + "winapi", ] [[package]] @@ -2321,7 +2280,7 @@ dependencies = [ "rand_os", "rand_pcg", "rand_xorshift", - "winapi 0.3.8", + "winapi", ] [[package]] @@ -2417,7 +2376,7 @@ checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" dependencies = [ "libc", "rand_core 0.4.2", - "winapi 0.3.8", + "winapi", ] [[package]] @@ -2431,7 +2390,7 @@ dependencies = [ "libc", "rand_core 0.4.2", "rdrand", - "winapi 0.3.8", + "winapi", ] [[package]] @@ -2503,7 +2462,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" dependencies = [ - "winapi 0.3.8", + "winapi", ] [[package]] @@ -2620,11 +2579,11 @@ dependencies = [ "libc", "log", "memchr", - "nix", + "nix 0.13.1", "unicode-segmentation", "unicode-width", "utf8parse", - "winapi 0.3.8", + "winapi", ] [[package]] @@ -2665,7 +2624,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" dependencies = [ "lazy_static", - "winapi 0.3.8", + "winapi", ] [[package]] @@ -2677,6 +2636,12 @@ dependencies = [ "parking_lot", ] +[[package]] +name = "scoped-tls-hkt" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e9d7eaddb227e8fbaaa71136ae0e1e913ca159b86c7da82f3e8f0044ad3a63" + [[package]] name = "scopeguard" version = "1.1.0" @@ -2722,6 +2687,12 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +[[package]] +name = "send_wrapper" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" + [[package]] name = "serde" version = "1.0.110" @@ -2830,6 +2801,37 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" +[[package]] +name = "smol" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686c634ad1873fffef6aed20f180eede424fbf3bb31802394c90fd7335a661b7" +dependencies = [ + "async-task", + "crossbeam", + "futures-io", + "futures-util", + "nix 0.17.0", + "once_cell", + "piper", + "scoped-tls-hkt", + "slab", + "socket2", + "wepoll-binding", +] + +[[package]] +name = "socket2" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "winapi", +] + [[package]] name = "spin" version = "0.5.2" @@ -2981,7 +2983,7 @@ dependencies = [ "rand 0.7.3", "redox_syscall", "remove_dir_all", - "winapi 0.3.8", + "winapi", ] [[package]] @@ -3029,7 +3031,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" dependencies = [ "libc", - "winapi 0.3.8", + "winapi", ] [[package]] @@ -3195,7 +3197,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" dependencies = [ "same-file", - "winapi 0.3.8", + "winapi", "winapi-util", ] @@ -3282,10 +3284,23 @@ dependencies = [ ] [[package]] -name = "winapi" -version = "0.2.8" +name = "wepoll-binding" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +checksum = "7511014d92edaa2cba607fd6dc62974915bebc3e16f91f911a4bb240b4a3a68c" +dependencies = [ + "bitflags", + "wepoll-sys", +] + +[[package]] +name = "wepoll-sys" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9082a777aed991f6769e2b654aa0cb29f1c3d615daf009829b07b66c7aff6a24" +dependencies = [ + "cc", +] [[package]] name = "winapi" @@ -3297,12 +3312,6 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu", ] -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" - [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" @@ -3315,7 +3324,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ - "winapi 0.3.8", + "winapi", ] [[package]] @@ -3330,17 +3339,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7daf138b6b14196e3830a588acf1e86966c694d3e8fb026fb105b8b5dca07e6e" dependencies = [ - "winapi 0.3.8", -] - -[[package]] -name = "ws2_32-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" -dependencies = [ - "winapi 0.2.8", - "winapi-build", + "winapi", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 6591225e3..f77843b51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,9 +23,9 @@ num-traits = "0.2.6" async-smtp = { version = "0.3" } email = { git = "https://github.com/deltachat/rust-email", branch = "master" } lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" } -async-imap = { git = "https://github.com/async-email/async-imap", rev = "1e8fdd86dbc99b6d92ab4667b426092060fe8b16" } -async-native-tls = { version = "0.3.1" } -async-std = { version = "1.5.0", features = ["unstable"] } +async-imap = "0.3.0" +async-native-tls = { version = "0.3.3" } +async-std = { version = "1.6.0", features = ["unstable"] } base64 = "0.11" charset = "0.1" percent-encoding = "2.0" @@ -59,7 +59,6 @@ thiserror = "1.0.14" anyhow = "1.0.28" async-trait = "0.1.31" url = "2.1.1" -crossbeam-channel = "0.4.2" pretty_env_logger = { version = "0.3.1", optional = true } log = {version = "0.4.8", optional = true } @@ -72,7 +71,7 @@ tempfile = "3.0" pretty_assertions = "0.6.1" pretty_env_logger = "0.3.0" proptest = "0.9.4" -async-std = { version = "1.5.0", features = ["unstable", "attributes"] } +async-std = { version = "1.6.0", features = ["unstable", "attributes"] } [workspace] members = [ diff --git a/deltachat-ffi/Cargo.toml b/deltachat-ffi/Cargo.toml index 3bd11869f..8fa08fab8 100644 --- a/deltachat-ffi/Cargo.toml +++ b/deltachat-ffi/Cargo.toml @@ -20,7 +20,7 @@ libc = "0.2" human-panic = "1.0.1" num-traits = "0.2.6" serde_json = "1.0" -async-std = "1.5.0" +async-std = "1.6.0" anyhow = "1.0.28" thiserror = "1.0.14" diff --git a/src/events.rs b/src/events.rs index c0e1e5901..156e19348 100644 --- a/src/events.rs +++ b/src/events.rs @@ -1,8 +1,7 @@ //! # Events specification use async_std::path::PathBuf; -use crossbeam_channel::{bounded as channel, Receiver, Sender, TrySendError}; - +use async_std::sync::{channel, Receiver, Sender, TrySendError}; use strum::EnumProperty; use crate::chat::ChatId; @@ -51,13 +50,13 @@ pub struct EventEmitter(Receiver); impl EventEmitter { /// Blocking recv of an event. Return `None` if the `Sender` has been droped. pub fn recv_sync(&self) -> Option { - self.0.recv().ok() + async_std::task::block_on(self.recv()) } /// Blocking async recv of an event. Return `None` if the `Sender` has been droped. pub async fn recv(&self) -> Option { // TODO: change once we can use async channels internally. - self.0.recv().ok() + self.0.recv().await.ok() } } diff --git a/src/scheduler.rs b/src/scheduler.rs index c1c84f193..358beefd0 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -13,6 +13,7 @@ pub(crate) struct StopToken; /// Job and connection scheduler. #[derive(Debug)] +#[allow(clippy::large_enum_variant)] pub(crate) enum Scheduler { Stopped, Running { @@ -339,12 +340,15 @@ impl Scheduler { } // wait for all loops to be started - inbox_start_recv + if let Err(err) = inbox_start_recv .recv() - .join(mvbox_start_recv.recv()) - .join(sentbox_start_recv.recv()) - .join(smtp_start_recv.recv()) - .await; + .try_join(mvbox_start_recv.recv()) + .try_join(sentbox_start_recv.recv()) + .try_join(smtp_start_recv.recv()) + .await + { + error!(ctx, "failed to start scheduler: {}", err); + } info!(ctx, "scheduler is running"); } @@ -478,7 +482,7 @@ impl ConnectionState { // Trigger shutdown of the run loop. self.stop_sender.send(()).await; // Wait for a notification that the run loop has been shutdown. - self.shutdown_receiver.recv().await; + self.shutdown_receiver.recv().await.ok(); } async fn interrupt(&self) { From e55dc2213a128b8e02df9d55ca845642ce14c607 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Sat, 23 May 2020 10:17:56 +0200 Subject: [PATCH 093/118] fix python lifecycles so that termination works --- python/src/deltachat/__init__.py | 11 +++--- python/src/deltachat/account.py | 48 +++++++++++-------------- python/src/deltachat/chat.py | 57 +++++++++++++++--------------- python/src/deltachat/contact.py | 7 ++-- python/src/deltachat/events.py | 18 ++++++---- python/src/deltachat/hookspec.py | 2 +- python/src/deltachat/message.py | 13 ++++--- python/src/deltachat/testplugin.py | 8 ++--- python/tests/test_account.py | 34 +++++++++--------- python/tests/test_lowlevel.py | 19 +++------- 10 files changed, 102 insertions(+), 115 deletions(-) diff --git a/python/src/deltachat/__init__.py b/python/src/deltachat/__init__.py index db637d017..0d8a84ecc 100644 --- a/python/src/deltachat/__init__.py +++ b/python/src/deltachat/__init__.py @@ -63,6 +63,10 @@ def run_cmdline(argv=None, account_plugins=None): log = events.FFIEventLogger(ac, "bot") ac.add_account_plugin(log) + for plugin in account_plugins or []: + print("adding plugin", plugin) + ac.add_account_plugin(plugin) + if not ac.is_configured(): assert args.email and args.password, ( "you must specify --email and --password once to configure this database/account" @@ -72,12 +76,11 @@ def run_cmdline(argv=None, account_plugins=None): ac.set_config("mvbox_move", "0") ac.set_config("mvbox_watch", "0") ac.set_config("sentbox_watch", "0") - - for plugin in account_plugins or []: - ac.add_account_plugin(plugin) + ac.configure() + ac.wait_configure_finish() # start IO threads and configure if neccessary - ac.start() + ac.start_io() print("{}: waiting for message".format(ac.get_config("addr"))) diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index c5bfe4109..f09f23615 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -1,7 +1,6 @@ """ Account class implementation. """ from __future__ import print_function -import atexit from contextlib import contextmanager from email.utils import parseaddr from threading import Event @@ -48,7 +47,7 @@ class Account(object): self._dc_context = ffi.gc( lib.dc_context_new(as_dc_charpointer(os_name), db_path, ffi.NULL), - _destroy_dc_context, + lib.dc_context_unref, ) if self._dc_context == ffi.NULL: raise ValueError("Could not dc_context_new: {} {}".format(os_name, db_path)) @@ -58,7 +57,6 @@ class Account(object): self._shutdown_event = Event() self._event_thread = EventThread(self) self._configkeys = self.get_config("sys.config_keys").split() - atexit.register(self.shutdown) hook.dc_account_init(account=self) def disable_logging(self): @@ -69,8 +67,8 @@ class Account(object): """ re-enable logging. """ self._logging = True - # def __del__(self): - # self.shutdown() + def __del__(self): + self.shutdown() def log(self, msg): if self._logging: @@ -241,7 +239,7 @@ class Account(object): :returns: True if deletion succeeded (contact was deleted) """ contact_id = contact.id - assert contact._dc_context == self._dc_context + assert contact.account == self assert contact_id > const.DC_CHAT_ID_LAST_SPECIAL return bool(lib.dc_delete_contact(self._dc_context, contact_id)) @@ -289,7 +287,7 @@ class Account(object): :returns: a :class:`deltachat.chat.Chat` object. """ if hasattr(contact, "id"): - if contact._dc_context != self._dc_context: + if contact.account != self: raise ValueError("Contact belongs to a different Account") contact_id = contact.id else: @@ -309,7 +307,7 @@ class Account(object): :returns: a :class:`deltachat.chat.Chat` object. """ if hasattr(message, "id"): - if self._dc_context != message._dc_context: + if message.account != self: raise ValueError("Message belongs to a different Account") msg_id = message.id else: @@ -557,13 +555,14 @@ class Account(object): """ Stop ongoing securejoin, configuration or other core jobs. """ lib.dc_stop_ongoing_process(self._dc_context) - def start(self): + def start_io(self): """ start this account's IO scheduling (Rust-core async scheduler) - If this account is not configured but "addr" and "mail_pw" config - values are set, dc_configure() will be called. + If this account is not configured an Exception is raised. + You need to call account.configure() and account.wait_configure_finish() + before. - You may call `wait_shutdown` or `shutdown` after the + You may call `stop_scheduler`, `wait_shutdown` or `shutdown` after the account is started. :raises MissingCredentials: if `addr` and `mail_pw` values are not set. @@ -572,8 +571,7 @@ class Account(object): :returns: None (account is configured and with io-scheduling running) """ if not self.is_configured(): - self.configure() - self.wait_configure_finish() + raise ValueError("account not configured, cannot start io") lib.dc_start_io(self._dc_context) def configure(self): @@ -601,13 +599,13 @@ class Account(object): """ wait until shutdown of this account has completed. """ self._shutdown_event.wait() - def stop_scheduler(self): - """ stop core scheduler if it is running. """ + def stop_io(self): + """ stop core IO scheduler if it is running. """ self.log("stop_ongoing") self.stop_ongoing() if bool(lib.dc_is_io_running(self._dc_context)): - self.log("context_shutdown (stop core scheduler)") + self.log("dc_stop_io (stop core IO scheduler)") lib.dc_stop_io(self._dc_context) else: self.log("stop_scheduler called on non-running context") @@ -615,13 +613,12 @@ class Account(object): def shutdown(self): """ shutdown and destroy account (stop callback thread, close and remove underlying dc_context).""" - dc_context = self._dc_context - if dc_context is None: + if self._dc_context is None: return - self.stop_scheduler() + self.stop_io() - self.log("remove dc_context") + self.log("remove dc_context references") # the dc_context_unref triggers get_next_event to return ffi.NULL # which in turns makes the event thread finish execution self._dc_context = None @@ -629,18 +626,13 @@ class Account(object): self.log("wait for event thread to finish") self._event_thread.wait() - atexit.unregister(self.shutdown) self._shutdown_event.set() + hook = hookspec.Global._get_plugin_manager().hook - hook.dc_account_after_shutdown(account=self, dc_context=dc_context) + hook.dc_account_after_shutdown(account=self) self.log("shutdown finished") -def _destroy_dc_context(dc_context, dc_context_unref=lib.dc_context_unref): - # destructor for dc_context - dc_context_unref(dc_context) - - class ScannedQRCode: def __init__(self, dc_lot): self._dc_lot = dc_lot diff --git a/python/src/deltachat/chat.py b/python/src/deltachat/chat.py index f2d6cd09a..21f4da558 100644 --- a/python/src/deltachat/chat.py +++ b/python/src/deltachat/chat.py @@ -19,12 +19,11 @@ class Chat(object): def __init__(self, account, id): self.account = account - self._dc_context = account._dc_context self.id = id def __eq__(self, other): return self.id == getattr(other, "id", None) and \ - self._dc_context == getattr(other, "_dc_context", None) + self.account._dc_context == other.account._dc_context def __ne__(self, other): return not (self == other) @@ -35,7 +34,7 @@ class Chat(object): @property def _dc_chat(self): return ffi.gc( - lib.dc_get_chat(self._dc_context, self.id), + lib.dc_get_chat(self.account._dc_context, self.id), lib.dc_chat_unref ) @@ -47,7 +46,7 @@ class Chat(object): - does not delete messages on server - the chat or contact is not blocked, new message will arrive """ - lib.dc_delete_chat(self._dc_context, self.id) + lib.dc_delete_chat(self.account._dc_context, self.id) # ------ chat status/metadata API ------------------------------ @@ -105,7 +104,7 @@ class Chat(object): :returns: None """ name = as_dc_charpointer(name) - return lib.dc_set_chat_name(self._dc_context, self.id, name) + return lib.dc_set_chat_name(self.account._dc_context, self.id, name) def mute(self, duration=None): """ mutes the chat @@ -117,7 +116,7 @@ class Chat(object): mute_duration = -1 else: mute_duration = duration - ret = lib.dc_set_chat_mute_duration(self._dc_context, self.id, mute_duration) + ret = lib.dc_set_chat_mute_duration(self.account._dc_context, self.id, mute_duration) if not bool(ret): raise ValueError("Call to dc_set_chat_mute_duration failed") @@ -126,7 +125,7 @@ class Chat(object): :returns: None """ - ret = lib.dc_set_chat_mute_duration(self._dc_context, self.id, 0) + ret = lib.dc_set_chat_mute_duration(self.account._dc_context, self.id, 0) if not bool(ret): raise ValueError("Failed to unmute chat") @@ -152,7 +151,7 @@ class Chat(object): in a second channel (typically used by mobiles with QRcode-show + scan UX) where account.join_with_qrcode(qr) needs to be called. """ - res = lib.dc_get_securejoin_qr(self._dc_context, self.id) + res = lib.dc_get_securejoin_qr(self.account._dc_context, self.id) return from_dc_charpointer(res) # ------ chat messaging API ------------------------------ @@ -174,7 +173,7 @@ class Chat(object): assert msg.id != 0 # get a fresh copy of dc_msg, the core needs it msg = Message.from_db(self.account, msg.id) - sent_id = lib.dc_send_msg(self._dc_context, self.id, msg._dc_msg) + sent_id = lib.dc_send_msg(self.account._dc_context, self.id, msg._dc_msg) if sent_id == 0: raise ValueError("message could not be sent") # modify message in place to avoid bad state for the caller @@ -189,7 +188,7 @@ class Chat(object): :returns: the resulting :class:`deltachat.message.Message` instance """ msg = as_dc_charpointer(text) - msg_id = lib.dc_send_text_msg(self._dc_context, self.id, msg) + msg_id = lib.dc_send_text_msg(self.account._dc_context, self.id, msg) if msg_id == 0: raise ValueError("message could not be send, does chat exist?") return Message.from_db(self.account, msg_id) @@ -204,7 +203,7 @@ class Chat(object): """ msg = Message.new_empty(self.account, view_type="file") msg.set_file(path, mime_type) - sent_id = lib.dc_send_msg(self._dc_context, self.id, msg._dc_msg) + sent_id = lib.dc_send_msg(self.account._dc_context, self.id, msg._dc_msg) if sent_id == 0: raise ValueError("message could not be sent") return Message.from_db(self.account, sent_id) @@ -219,7 +218,7 @@ class Chat(object): mime_type = mimetypes.guess_type(path)[0] msg = Message.new_empty(self.account, view_type="image") msg.set_file(path, mime_type) - sent_id = lib.dc_send_msg(self._dc_context, self.id, msg._dc_msg) + sent_id = lib.dc_send_msg(self.account._dc_context, self.id, msg._dc_msg) if sent_id == 0: raise ValueError("message could not be sent") return Message.from_db(self.account, sent_id) @@ -230,7 +229,7 @@ class Chat(object): :param msg: the message to be prepared. :returns: :class:`deltachat.message.Message` instance. """ - msg_id = lib.dc_prepare_msg(self._dc_context, self.id, msg._dc_msg) + msg_id = lib.dc_prepare_msg(self.account._dc_context, self.id, msg._dc_msg) if msg_id == 0: raise ValueError("message could not be prepared") # invalidate passed in message which is not safe to use anymore @@ -266,7 +265,7 @@ class Chat(object): msg = Message.from_db(self.account, message.id) # pass 0 as chat-id because core-docs say it's ok when out-preparing - sent_id = lib.dc_send_msg(self._dc_context, 0, msg._dc_msg) + sent_id = lib.dc_send_msg(self.account._dc_context, 0, msg._dc_msg) if sent_id == 0: raise ValueError("message could not be sent") assert sent_id == msg.id @@ -280,9 +279,9 @@ class Chat(object): :returns: None """ if message is None: - lib.dc_set_draft(self._dc_context, self.id, ffi.NULL) + lib.dc_set_draft(self.account._dc_context, self.id, ffi.NULL) else: - lib.dc_set_draft(self._dc_context, self.id, message._dc_msg) + lib.dc_set_draft(self.account._dc_context, self.id, message._dc_msg) def get_draft(self): """ get draft message for this chat. @@ -290,7 +289,7 @@ class Chat(object): :param message: a :class:`Message` instance :returns: Message object or None (if no draft available) """ - x = lib.dc_get_draft(self._dc_context, self.id) + x = lib.dc_get_draft(self.account._dc_context, self.id) if x == ffi.NULL: return None dc_msg = ffi.gc(x, lib.dc_msg_unref) @@ -302,7 +301,7 @@ class Chat(object): :returns: list of :class:`deltachat.message.Message` objects for this chat. """ dc_array = ffi.gc( - lib.dc_get_chat_msgs(self._dc_context, self.id, 0, 0), + lib.dc_get_chat_msgs(self.account._dc_context, self.id, 0, 0), lib.dc_array_unref ) return list(iter_array(dc_array, lambda x: Message.from_db(self.account, x))) @@ -312,18 +311,18 @@ class Chat(object): :returns: number of fresh messages """ - return lib.dc_get_fresh_msg_cnt(self._dc_context, self.id) + return lib.dc_get_fresh_msg_cnt(self.account._dc_context, self.id) def mark_noticed(self): """ mark all messages in this chat as noticed. Noticed messages are no longer fresh. """ - return lib.dc_marknoticed_chat(self._dc_context, self.id) + return lib.dc_marknoticed_chat(self.account._dc_context, self.id) def get_summary(self): """ return dictionary with summary information. """ - dc_res = lib.dc_chat_get_info_json(self._dc_context, self.id) + dc_res = lib.dc_chat_get_info_json(self.account._dc_context, self.id) s = from_dc_charpointer(dc_res) return json.loads(s) @@ -336,7 +335,7 @@ class Chat(object): :raises ValueError: if contact could not be added :returns: None """ - ret = lib.dc_add_contact_to_chat(self._dc_context, self.id, contact.id) + ret = lib.dc_add_contact_to_chat(self.account._dc_context, self.id, contact.id) if ret != 1: raise ValueError("could not add contact {!r} to chat".format(contact)) @@ -347,7 +346,7 @@ class Chat(object): :raises ValueError: if contact could not be removed :returns: None """ - ret = lib.dc_remove_contact_from_chat(self._dc_context, self.id, contact.id) + ret = lib.dc_remove_contact_from_chat(self.account._dc_context, self.id, contact.id) if ret != 1: raise ValueError("could not remove contact {!r} from chat".format(contact)) @@ -359,7 +358,7 @@ class Chat(object): """ from .contact import Contact dc_array = ffi.gc( - lib.dc_get_chat_contacts(self._dc_context, self.id), + lib.dc_get_chat_contacts(self.account._dc_context, self.id), lib.dc_array_unref ) return list(iter_array( @@ -378,7 +377,7 @@ class Chat(object): """ assert os.path.exists(img_path), img_path p = as_dc_charpointer(img_path) - res = lib.dc_set_chat_profile_image(self._dc_context, self.id, p) + res = lib.dc_set_chat_profile_image(self.account._dc_context, self.id, p) if res != 1: raise ValueError("Setting Profile Image {!r} failed".format(p)) @@ -391,7 +390,7 @@ class Chat(object): :raises ValueError: if profile image could not be reset :returns: None """ - res = lib.dc_set_chat_profile_image(self._dc_context, self.id, ffi.NULL) + res = lib.dc_set_chat_profile_image(self.account._dc_context, self.id, ffi.NULL) if res != 1: raise ValueError("Removing Profile Image failed") @@ -421,7 +420,7 @@ class Chat(object): """return True if this chat has location-sending enabled currently. :returns: True if location sending is enabled. """ - return lib.dc_is_sending_locations_to_chat(self._dc_context, self.id) + return lib.dc_is_sending_locations_to_chat(self.account._dc_context, self.id) def is_archived(self): """return True if this chat is archived. @@ -434,7 +433,7 @@ class Chat(object): all subsequent messages will carry a location with them. """ - lib.dc_send_locations_to_chat(self._dc_context, self.id, seconds) + lib.dc_send_locations_to_chat(self.account._dc_context, self.id, seconds) def get_locations(self, contact=None, timestamp_from=None, timestamp_to=None): """return list of locations for the given contact in the given timespan. @@ -458,7 +457,7 @@ class Chat(object): else: contact_id = contact.id - dc_array = lib.dc_get_locations(self._dc_context, self.id, contact_id, time_from, time_to) + dc_array = lib.dc_get_locations(self.account._dc_context, self.id, contact_id, time_from, time_to) return [ Location( latitude=lib.dc_array_get_latitude(dc_array, i), diff --git a/python/src/deltachat/contact.py b/python/src/deltachat/contact.py index 6e2cf1ae5..92e46f50c 100644 --- a/python/src/deltachat/contact.py +++ b/python/src/deltachat/contact.py @@ -12,22 +12,21 @@ class Contact(object): """ def __init__(self, account, id): self.account = account - self._dc_context = account._dc_context self.id = id def __eq__(self, other): - return self._dc_context == other._dc_context and self.id == other.id + return self.account._dc_context == other.account._dc_context and self.id == other.id def __ne__(self, other): return not (self == other) def __repr__(self): - return "".format(self.id, self.addr, self._dc_context) + return "".format(self.id, self.addr, self.account._dc_context) @property def _dc_contact(self): return ffi.gc( - lib.dc_get_contact(self._dc_context, self.id), + lib.dc_get_contact(self.account._dc_context, self.id), lib.dc_contact_unref ) diff --git a/python/src/deltachat/events.py b/python/src/deltachat/events.py index 10a59fc2c..d68f83dd3 100644 --- a/python/src/deltachat/events.py +++ b/python/src/deltachat/events.py @@ -133,8 +133,8 @@ class EventThread(threading.Thread): """ def __init__(self, account): self.account = account - self._dc_context = account._dc_context super(EventThread, self).__init__(name="events") + self.setDaemon(True) self.start() @contextmanager @@ -157,7 +157,7 @@ class EventThread(threading.Thread): def _inner_run(self): event_emitter = ffi.gc( - lib.dc_get_event_emitter(self._dc_context), + lib.dc_get_event_emitter(self.account._dc_context), lib.dc_event_emitter_unref, ) while 1: @@ -176,11 +176,15 @@ class EventThread(threading.Thread): lib.dc_event_unref(event) ffi_event = FFIEvent(name=evt_name, data1=data1, data2=data2) - self.account._pm.hook.ac_process_ffi_event(account=self, ffi_event=ffi_event) - for name, kwargs in self._map_ffi_event(ffi_event): - # self.account.log("calling hook name={} kwargs={}".format(name, kwargs)) - hook = getattr(self.account._pm.hook, name) - hook(**kwargs) + try: + self.account._pm.hook.ac_process_ffi_event(account=self, ffi_event=ffi_event) + for name, kwargs in self._map_ffi_event(ffi_event): + self.account.log("calling hook name={} kwargs={}".format(name, kwargs)) + hook = getattr(self.account._pm.hook, name) + hook(**kwargs) + except Exception: + if self.account._dc_context is not None: + raise def _map_ffi_event(self, ffi_event): name = ffi_event.name diff --git a/python/src/deltachat/hookspec.py b/python/src/deltachat/hookspec.py index b0e896aaf..b20d56027 100644 --- a/python/src/deltachat/hookspec.py +++ b/python/src/deltachat/hookspec.py @@ -89,5 +89,5 @@ class Global: """ called when `Account::__init__()` function starts executing. """ @global_hookspec - def dc_account_after_shutdown(self, account, dc_context): + def dc_account_after_shutdown(self, account): """ Called after the account has been shutdown. """ diff --git a/python/src/deltachat/message.py b/python/src/deltachat/message.py index 91cc738c0..706f8c8f5 100644 --- a/python/src/deltachat/message.py +++ b/python/src/deltachat/message.py @@ -16,8 +16,7 @@ class Message(object): """ def __init__(self, account, dc_msg): self.account = account - self._dc_context = account._dc_context - assert isinstance(self._dc_context, ffi.CData) + assert isinstance(self.account._dc_context, ffi.CData) assert isinstance(dc_msg, ffi.CData) assert dc_msg != ffi.NULL self._dc_msg = dc_msg @@ -58,7 +57,7 @@ class Message(object): """ self.account.create_chat_by_message(self) self._dc_msg = ffi.gc( - lib.dc_get_msg(self._dc_context, self.id), + lib.dc_get_msg(self.account._dc_context, self.id), lib.dc_msg_unref ) @@ -118,12 +117,12 @@ class Message(object): The text is multiline and may contain eg. the raw text of the message. """ - return from_dc_charpointer(lib.dc_get_msg_info(self._dc_context, self.id)) + return from_dc_charpointer(lib.dc_get_msg_info(self.account._dc_context, self.id)) def continue_key_transfer(self, setup_code): """ extract key and use it as primary key for this account. """ res = lib.dc_continue_key_transfer( - self._dc_context, + self.account._dc_context, self.id, as_dc_charpointer(setup_code) ) @@ -158,7 +157,7 @@ class Message(object): :returns: email-mime message object (with headers only, no body). """ import email.parser - mime_headers = lib.dc_get_mime_headers(self._dc_context, self.id) + mime_headers = lib.dc_get_mime_headers(self.account._dc_context, self.id) if mime_headers: s = ffi.string(ffi.gc(mime_headers, lib.dc_str_unref)) if isinstance(s, bytes): @@ -201,7 +200,7 @@ class Message(object): else: # load message from db to get a fresh/current state dc_msg = ffi.gc( - lib.dc_get_msg(self._dc_context, self.id), + lib.dc_get_msg(self.account._dc_context, self.id), lib.dc_msg_unref ) return lib.dc_msg_get_state(dc_msg) diff --git a/python/src/deltachat/testplugin.py b/python/src/deltachat/testplugin.py index ddc8ee9fc..a785d9cf0 100644 --- a/python/src/deltachat/testplugin.py +++ b/python/src/deltachat/testplugin.py @@ -103,7 +103,6 @@ def pytest_report_header(config, startdir): t = tempfile.mktemp() m = MonkeyPatch() try: - m.setattr(sys.stdout, "write", lambda x: len(x)) ac = Account(t) info = ac.get_info() ac.shutdown() @@ -310,16 +309,16 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, data): ac1 = self.get_online_configuring_account( pre_generated_key=pre_generated_key, mvbox=mvbox, move=move) ac1.wait_configure_finish() - ac1.start() + ac1.start_io() return ac1 def get_two_online_accounts(self, move=False, quiet=False): ac1 = self.get_online_configuring_account(move=True, quiet=quiet) ac2 = self.get_online_configuring_account(quiet=quiet) ac1.wait_configure_finish() - ac1.start() + ac1.start_io() ac2.wait_configure_finish() - ac2.start() + ac2.start_io() return ac1, ac2 def clone_online_account(self, account, pre_generated_key=True): @@ -394,6 +393,7 @@ class BotProcess: break line = line.strip() self.stdout_queue.put(line) + print("bot-stdout: ", line) finally: self.stdout_queue.put(None) diff --git a/python/tests/test_account.py b/python/tests/test_account.py index 6315d136e..d134bc0a1 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -533,9 +533,9 @@ class TestOnlineAccount: # rsa key gen can be slow especially on CI, adjust timeout ac1._evtracker.set_timeout(120) ac1.wait_configure_finish() - ac1.start() + ac1.start_io() ac2.wait_configure_finish() - ac2.start() + ac2.start_io() chat = self.get_chat(ac1, ac2, both_created=True) lp.sec("ac1: send unencrypted message to ac2") @@ -591,11 +591,11 @@ class TestOnlineAccount: ac1_clone = acfactory.clone_online_account(ac1) ac1.wait_configure_finish() - ac1.start() + ac1.start_io() ac2.wait_configure_finish() - ac2.start() + ac2.start_io() ac1_clone.wait_configure_finish() - ac1_clone.start() + ac1_clone.start_io() chat = self.get_chat(ac1, ac2) @@ -700,11 +700,11 @@ class TestOnlineAccount: lp.sec("ac2: waiting for configuration") ac2.wait_configure_finish() - ac2.start() + ac2.start_io() lp.sec("ac1: waiting for configuration") ac1.wait_configure_finish() - ac1.start() + ac1.start_io() lp.sec("ac1: send message and wait for ac2 to receive it") chat = self.get_chat(ac1, ac2) @@ -717,9 +717,9 @@ class TestOnlineAccount: ac1 = acfactory.get_online_configuring_account() ac2 = acfactory.get_online_configuring_account(mvbox=True, move=True) ac2.wait_configure_finish() - ac2.start() + ac2.start_io() ac1.wait_configure_finish() - ac1.start() + ac1.start_io() chat = self.get_chat(ac1, ac2) chat.send_text("message1") ev = ac2._evtracker.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED") @@ -731,9 +731,9 @@ class TestOnlineAccount: ac1.set_config("bcc_self", "1") ac2 = acfactory.get_online_configuring_account() ac2.wait_configure_finish() - ac2.start() + ac2.start_io() ac1.wait_configure_finish() - ac1.start() + ac1.start_io() chat = self.get_chat(ac1, ac2) chat.send_text("message1") @@ -1162,9 +1162,9 @@ class TestOnlineAccount: ac1 = acfactory.get_online_configuring_account() ac2 = acfactory.clone_online_account(ac1) ac2.wait_configure_finish() - ac2.start() + ac2.start_io() ac1.wait_configure_finish() - ac1.start() + ac1.start_io() lp.sec("trigger ac setup message and return setupcode") assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"] @@ -1188,9 +1188,9 @@ class TestOnlineAccount: ac2 = acfactory.clone_online_account(ac1) ac2._evtracker.set_timeout(30) ac2.wait_configure_finish() - ac2.start() + ac2.start_io() ac1.wait_configure_finish() - ac1.start() + ac1.start_io() lp.sec("trigger ac setup message but ignore") assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"] @@ -1502,7 +1502,7 @@ class TestGroupStressTests: accounts = [acfactory.get_online_configuring_account() for i in range(5)] for acc in accounts: acc.wait_configure_finish() - acc.start() + acc.start_io() ac1 = accounts.pop() lp.sec("ac1: setting up contacts with 4 other members") @@ -1610,7 +1610,7 @@ class TestGroupStressTests: accounts = [acfactory.get_online_configuring_account() for i in range(3)] for acc in accounts: acc.wait_configure_finish() - acc.start() + acc.start_io() ac1 = accounts.pop() lp.sec("ac1: setting up contacts with 2 other members") diff --git a/python/tests/test_lowlevel.py b/python/tests/test_lowlevel.py index b8b66b61b..2e7640e36 100644 --- a/python/tests/test_lowlevel.py +++ b/python/tests/test_lowlevel.py @@ -36,13 +36,14 @@ def test_wrong_db(tmpdir): # write an invalid database file p.write("x123" * 10) - assert ffi.NULL == lib.dc_context_new(ffi.NULL, ffi.NULL, p.strpath.encode("ascii"), ffi.NULL) + assert ffi.NULL == lib.dc_context_new(ffi.NULL, p.strpath.encode("ascii"), ffi.NULL) + def test_empty_blobdir(tmpdir): db_fname = tmpdir.join("hello.db") # Apparently some client code expects this to be the same as passing NULL. ctx = ffi.gc( - lib.dc_context_new(ffi.NULL, ffi.NULL, db_fname.strpath.encode("ascii"), b""), + lib.dc_context_new(ffi.NULL, db_fname.strpath.encode("ascii"), b""), lib.dc_context_unref, ) assert ctx != ffi.NULL @@ -86,26 +87,16 @@ def test_get_special_message_id_returns_empty_message(acfactory): def test_provider_info_none(): ctx = ffi.gc( - lib.dc_context_new(ffi.NULL, ffi.NULL), + lib.dc_context_new(ffi.NULL, ffi.NULL, ffi.NULL), lib.dc_context_unref, ) assert lib.dc_provider_new_from_email(ctx, cutil.as_dc_charpointer("email@unexistent.no")) == ffi.NULL -def test_get_info_closed(): - ctx = ffi.gc( - lib.dc_context_new(ffi.NULL, ffi.NULL), - lib.dc_context_unref, - ) - info = cutil.from_dc_charpointer(lib.dc_get_info(ctx)) - assert 'deltachat_core_version' in info - assert 'database_dir' not in info - - def test_get_info_open(tmpdir): db_fname = tmpdir.join("test.db") ctx = ffi.gc( - lib.dc_context_new(ffi.NULL, ffi.NULL, db_fname.strpath.encode("ascii"), ffi.NULL), + lib.dc_context_new(ffi.NULL, db_fname.strpath.encode("ascii"), ffi.NULL), lib.dc_context_unref, ) info = cutil.from_dc_charpointer(lib.dc_get_info(ctx)) From 0ea442ca36413b823a9ad2700314e045671fac5c Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Sat, 23 May 2020 18:56:45 +0200 Subject: [PATCH 094/118] feat: add chat::send_msg_sync --- deltachat-ffi/deltachat.h | 17 ++++++++ deltachat-ffi/src/lib.rs | 21 ++++++++++ src/chat.rs | 82 ++++++++++++++++++++++++++++++------ src/context.rs | 5 +-- src/dc_receive_imf.rs | 10 +++-- src/imap/mod.rs | 5 +-- src/job.rs | 87 +++++++++++++++++---------------------- src/location.rs | 15 ++++--- src/message.rs | 22 +++++----- 9 files changed, 172 insertions(+), 92 deletions(-) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 5dc6d343c..d084dfc68 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -802,6 +802,23 @@ uint32_t dc_prepare_msg (dc_context_t* context, uint32_t ch */ uint32_t dc_send_msg (dc_context_t* context, uint32_t chat_id, dc_msg_t* msg); +/** + * Send a message defined by a dc_msg_t object to a chat, synchronously. + * This bypasses the IO scheduler and creates its own SMTP connection. Which means + * this is useful when the scheduler is not running. + * + * @memberof dc_context_t + * @param context The context object as returned from dc_context_new(). + * @param chat_id Chat ID to send the message to. + * If dc_prepare_msg() was called before, this parameter can be 0. + * @param msg Message object to send to the chat defined by the chat ID. + * On succcess, msg_id of the object is set up, + * The function does not take ownership of the object, + * so you have to free it using dc_msg_unref() as usual. + * @return The ID of the message that is about to be sent. 0 in case of errors. + */ +uint32_t dc_send_msg_sync (dc_context_t* context, uint32_t chat_id, dc_msg_t* msg); + /** * Send a simple text message a given chat. diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index f996db069..5e4a6b51b 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -670,6 +670,27 @@ pub unsafe extern "C" fn dc_send_msg( .to_u32() } +#[no_mangle] +pub unsafe extern "C" fn dc_send_msg_sync( + context: *mut dc_context_t, + chat_id: u32, + msg: *mut dc_msg_t, +) -> u32 { + if context.is_null() || msg.is_null() { + eprintln!("ignoring careless call to dc_send_msg_sync()"); + return 0; + } + let ctx = &mut *context; + let ffi_msg = &mut *msg; + + block_on(async move { + chat::send_msg_sync(&ctx, ChatId::new(chat_id), &mut ffi_msg.message) + .await + .unwrap_or_log_default(&ctx, "Failed to send message") + }) + .to_u32() +} + #[no_mangle] pub unsafe extern "C" fn dc_send_text_msg( context: *mut dc_context_t, diff --git a/src/chat.rs b/src/chat.rs index 00921bc6c..ae1efd74f 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -237,7 +237,8 @@ impl ChatId { }); job::kill_action(context, Action::Housekeeping).await; - job::add(context, Action::Housekeeping, 0, Params::new(), 10).await; + let j = job::Job::new(Action::Housekeeping, 0, Params::new(), 10); + job::add(context, j).await; Ok(()) } @@ -1454,11 +1455,73 @@ pub async fn send_msg( send_msg_inner(context, chat_id, msg).await } +/// Tries to send a message synchronously. +/// +/// Directly opens an smtp +/// connection and sends the message, bypassing the job system. If this fails, it writes a send job to +/// the database. +pub async fn send_msg_sync( + context: &Context, + chat_id: ChatId, + msg: &mut Message, +) -> Result { + if context.is_io_running().await { + return send_msg(context, chat_id, msg).await; + } + + if let Some(mut job) = prepare_send_msg(context, chat_id, msg).await? { + let mut smtp = crate::smtp::Smtp::new(); + + let status = job.send_msg_to_smtp(context, &mut smtp).await; + + match status { + job::Status::Finished(Ok(_)) => { + context.emit_event(Event::MsgsChanged { + chat_id: msg.chat_id, + msg_id: msg.id, + }); + + Ok(msg.id) + } + _ => { + job.save(context).await?; + Err(format_err!( + "failed to send message, queued for later sending" + )) + } + } + } else { + // Nothing to do + Ok(msg.id) + } +} + async fn send_msg_inner( context: &Context, chat_id: ChatId, msg: &mut Message, ) -> Result { + if let Some(send_job) = prepare_send_msg(context, chat_id, msg).await? { + job::add(context, send_job).await; + + context.emit_event(Event::MsgsChanged { + chat_id: msg.chat_id, + msg_id: msg.id, + }); + + if msg.param.exists(Param::SetLatitude) { + context.emit_event(Event::LocationChanged(Some(DC_CONTACT_ID_SELF))); + } + } + + Ok(msg.id) +} + +async fn prepare_send_msg( + context: &Context, + chat_id: ChatId, + msg: &mut Message, +) -> Result, Error> { // dc_prepare_msg() leaves the message state to OutPreparing, we // only have to change the state to OutPending in this case. // Otherwise we still have to prepare the message, which will set @@ -1474,18 +1537,9 @@ async fn send_msg_inner( ); message::update_msg_state(context, msg.id, MessageState::OutPending).await; } - job::send_msg(context, msg.id).await?; + let job = job::send_msg_job(context, msg.id).await?; - context.emit_event(Event::MsgsChanged { - chat_id: msg.chat_id, - msg_id: msg.id, - }); - - if msg.param.exists(Param::SetLatitude) { - context.emit_event(Event::LocationChanged(Some(DC_CONTACT_ID_SELF))); - } - - Ok(msg.id) + Ok(job) } pub async fn send_text_msg( @@ -2533,7 +2587,9 @@ pub async fn forward_msgs( let fresh10 = curr_timestamp; curr_timestamp += 1; new_msg_id = chat.prepare_msg_raw(context, &mut msg, fresh10).await?; - job::send_msg(context, new_msg_id).await?; + if let Some(send_job) = job::send_msg_job(context, new_msg_id).await? { + job::add(context, send_job).await; + } } created_chats.push(chat_id); created_msgs.push(new_msg_id); diff --git a/src/context.rs b/src/context.rs index 1fa3d6c8f..2eafdb22d 100644 --- a/src/context.rs +++ b/src/context.rs @@ -481,10 +481,7 @@ impl Context { MessengerMessage::Yes | MessengerMessage::Reply => { job::add( self, - Action::MoveMsg, - msg.id.to_u32() as i32, - Params::new(), - 0, + job::Job::new(Action::MoveMsg, msg.id.to_u32(), Params::new(), 0), ) .await; } diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index bcb6e39a2..813ed6732 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -213,10 +213,12 @@ pub async fn dc_receive_imf( for db_entry in &created_db_entries { job::add( context, - Action::DeleteMsgOnImap, - db_entry.1.to_u32() as i32, - Params::new(), - 0, + job::Job::new( + Action::DeleteMsgOnImap, + db_entry.1.to_u32(), + Params::new(), + 0, + ), ) .await; } diff --git a/src/imap/mod.rs b/src/imap/mod.rs index 794e01d8c..d0e98cb36 100644 --- a/src/imap/mod.rs +++ b/src/imap/mod.rs @@ -1316,10 +1316,7 @@ async fn precheck_imf( .await; job::add( context, - Action::MarkseenMsgOnImap, - msg_id.to_u32() as i32, - Params::new(), - 0, + job::Job::new(Action::MarkseenMsgOnImap, msg_id.to_u32(), Params::new(), 0), ) .await; } diff --git a/src/job.rs b/src/job.rs index 98a7a3ad4..e8f6f0d25 100644 --- a/src/job.rs +++ b/src/job.rs @@ -28,7 +28,7 @@ use crate::location; use crate::login_param::LoginParam; use crate::message::MsgId; use crate::message::{self, Message, MessageState}; -use crate::mimefactory::{MimeFactory, RenderedEmail}; +use crate::mimefactory::MimeFactory; use crate::param::*; use crate::smtp::Smtp; use crate::sql; @@ -156,7 +156,7 @@ impl fmt::Display for Job { } impl Job { - fn new(action: Action, foreign_id: u32, param: Params, delay_seconds: i64) -> Self { + pub fn new(action: Action, foreign_id: u32, param: Params, delay_seconds: i64) -> Self { let timestamp = time(); Self { @@ -171,6 +171,10 @@ impl Job { } } + pub fn delay_seconds(&self) -> i64 { + self.desired_timestamp - self.added_timestamp + } + /// Deletes the job from the database. async fn delete(self, context: &Context) -> Result<()> { if self.job_id != 0 { @@ -186,7 +190,7 @@ impl Job { /// Saves the job to the database, creating a new entry if necessary. /// /// The Job is consumed by this method. - async fn save(self, context: &Context) -> Result<()> { + pub async fn save(self, context: &Context) -> Result<()> { let thread: Thread = self.action.into(); info!(context, "saving job for {}-thread: {:?}", thread, self); @@ -325,7 +329,7 @@ impl Job { } } - async fn send_msg_to_smtp(&mut self, context: &Context, smtp: &mut Smtp) -> Status { + pub async fn send_msg_to_smtp(&mut self, context: &Context, smtp: &mut Smtp) -> Status { // SMTP server, if not yet done if !smtp.is_connected().await { let loginparam = LoginParam::from_database(context, "configured_").await; @@ -694,8 +698,12 @@ async fn set_delivered(context: &Context, msg_id: MsgId) { context.emit_event(Event::MsgDelivered { chat_id, msg_id }); } -// special case for DC_JOB_SEND_MSG_TO_SMTP -pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> { +/// Constructs a job for sending a message. +/// +/// Returns `None` if no messages need to be sent out. +/// +/// In order to be processed, must be `add`ded. +pub async fn send_msg_job(context: &Context, msg_id: MsgId) -> Result> { let mut msg = Message::load_from_db(context, msg_id).await?; msg.try_calc_and_set_dimensions(context).await.ok(); @@ -738,7 +746,7 @@ pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> { "message {} has no recipient, skipping smtp-send", msg_id ); set_delivered(context, msg_id).await; - return Ok(()); + return Ok(None); } let rendered_msg = match mimefactory.render().await { @@ -793,16 +801,18 @@ pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> { msg.save_param_to_disk(context).await; } - add_smtp_job( - context, - Action::SendMsgToSmtp, - msg.id, - recipients, - &rendered_msg, - ) - .await?; + ensure!(!recipients.is_empty(), "no recipients for smtp job set"); + let mut param = Params::new(); + let bytes = &rendered_msg.message; + let blob = BlobObject::create(context, &rendered_msg.rfc724_mid, bytes).await?; - Ok(()) + let recipients = recipients.join("\x1e"); + param.set(Param::File, blob.as_name()); + param.set(Param::Recipients, &recipients); + + let job = create(Action::SendMsgToSmtp, msg_id.to_u32() as i32, param, 0)?; + + Ok(Some(job)) } #[derive(Debug)] @@ -986,46 +996,25 @@ async fn send_mdn(context: &Context, msg: &Message) -> Result<()> { let mut param = Params::new(); param.set(Param::MsgId, msg.id.to_u32().to_string()); - add(context, Action::SendMdn, msg.from_id as i32, param, 0).await; + add(context, Job::new(Action::SendMdn, msg.from_id, param, 0)).await; Ok(()) } -async fn add_smtp_job( - context: &Context, - action: Action, - msg_id: MsgId, - recipients: Vec, - rendered_msg: &RenderedEmail, -) -> Result<()> { - ensure!(!recipients.is_empty(), "no recipients for smtp job set"); - let mut param = Params::new(); - let bytes = &rendered_msg.message; - let blob = BlobObject::create(context, &rendered_msg.rfc724_mid, bytes).await?; +/// Creates a job. +pub fn create(action: Action, foreign_id: i32, param: Params, delay_seconds: i64) -> Result { + ensure!( + action != Action::Unknown, + "Invalid action passed to job_add" + ); - let recipients = recipients.join("\x1e"); - param.set(Param::File, blob.as_name()); - param.set(Param::Recipients, &recipients); - - add(context, action, msg_id.to_u32() as i32, param, 0).await; - - Ok(()) + Ok(Job::new(action, foreign_id as u32, param, delay_seconds)) } -/// Adds a job to the database, scheduling it `delay_seconds` after the current time. -pub async fn add( - context: &Context, - action: Action, - foreign_id: i32, - param: Params, - delay_seconds: i64, -) { - if action == Action::Unknown { - error!(context, "Invalid action passed to job_add"); - return; - } - - let job = Job::new(action, foreign_id as u32, param, delay_seconds); +/// Adds a job to the database, scheduling it. +pub async fn add(context: &Context, job: Job) { + let action = job.action; + let delay_seconds = job.delay_seconds(); job.save(context).await.unwrap_or_else(|err| { error!(context, "failed to save job: {}", err); }); diff --git a/src/location.rs b/src/location.rs index d36450bbf..c27f90c41 100644 --- a/src/location.rs +++ b/src/location.rs @@ -232,10 +232,12 @@ pub async fn send_locations_to_chat(context: &Context, chat_id: ChatId, seconds: schedule_maybe_send_locations(context, false).await; job::add( context, - job::Action::MaybeSendLocationsEnded, - chat_id.to_u32() as i32, - Params::new(), - seconds + 1, + job::Job::new( + job::Action::MaybeSendLocationsEnded, + chat_id.to_u32(), + Params::new(), + seconds + 1, + ), ) .await; } @@ -247,10 +249,7 @@ async fn schedule_maybe_send_locations(context: &Context, force_schedule: bool) if force_schedule || !job::action_exists(context, job::Action::MaybeSendLocations).await { job::add( context, - job::Action::MaybeSendLocations, - 0, - Params::new(), - 60, + job::Job::new(job::Action::MaybeSendLocations, 0, Params::new(), 60), ) .await; }; diff --git a/src/message.rs b/src/message.rs index 59951b265..1c77b4017 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1021,10 +1021,7 @@ pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) { } job::add( context, - Action::DeleteMsgOnImap, - msg_id.to_u32() as i32, - Params::new(), - 0, + job::Job::new(Action::DeleteMsgOnImap, msg_id.to_u32(), Params::new(), 0), ) .await; } @@ -1035,7 +1032,11 @@ pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) { msg_id: MsgId::new(0), }); job::kill_action(context, Action::Housekeeping).await; - job::add(context, Action::Housekeeping, 0, Params::new(), 10).await; + job::add( + context, + job::Job::new(Action::Housekeeping, 0, Params::new(), 10), + ) + .await; } } @@ -1097,10 +1098,7 @@ pub async fn markseen_msgs(context: &Context, msg_ids: Vec) -> bool { job::add( context, - Action::MarkseenMsgOnImap, - id.to_u32() as i32, - Params::new(), - 0, + job::Job::new(Action::MarkseenMsgOnImap, id.to_u32(), Params::new(), 0), ) .await; send_event = true; @@ -1550,7 +1548,11 @@ pub async fn update_server_uid( #[allow(dead_code)] pub async fn dc_empty_server(context: &Context, flags: u32) { job::kill_action(context, Action::EmptyServer).await; - job::add(context, Action::EmptyServer, flags as i32, Params::new(), 0).await; + job::add( + context, + job::Job::new(Action::EmptyServer, flags, Params::new(), 0), + ) + .await; } #[cfg(test)] From d4ddc2f9dafba08259ff34b7c6f334438746a2dd Mon Sep 17 00:00:00 2001 From: holger krekel Date: Sat, 23 May 2020 21:57:50 +0200 Subject: [PATCH 095/118] make wheel building work again -- switch manylinux2014 (#1522) --- ci_scripts/docker-coredeps/Dockerfile | 2 +- ci_scripts/docker-coredeps/deps/build_rust.sh | 4 +-- ci_scripts/remote_python_packaging.sh | 2 +- ci_scripts/run_all.sh | 3 +- python/README.rst | 6 ++-- python/examples/echo_and_quit.py | 7 ++-- python/src/deltachat/message.py | 8 +++-- python/tests/auditwheels.py | 4 ++- python/tests/test_account.py | 32 +++++++++++++++++++ 9 files changed, 54 insertions(+), 14 deletions(-) diff --git a/ci_scripts/docker-coredeps/Dockerfile b/ci_scripts/docker-coredeps/Dockerfile index 50f53d1d5..293d9b0a4 100644 --- a/ci_scripts/docker-coredeps/Dockerfile +++ b/ci_scripts/docker-coredeps/Dockerfile @@ -1,4 +1,4 @@ -FROM quay.io/pypa/manylinux1_x86_64 +FROM quay.io/pypa/manylinux2010_x86_64 # Configure ld.so/ldconfig and pkg-config RUN echo /usr/local/lib64 > /etc/ld.so.conf.d/local.conf && \ diff --git a/ci_scripts/docker-coredeps/deps/build_rust.sh b/ci_scripts/docker-coredeps/deps/build_rust.sh index 93e8ed281..72d3e599d 100755 --- a/ci_scripts/docker-coredeps/deps/build_rust.sh +++ b/ci_scripts/docker-coredeps/deps/build_rust.sh @@ -3,9 +3,9 @@ set -e -x # Install Rust -curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly-2020-03-19 -y +curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain 1.43.1-x86_64-unknown-linux-gnu -y export PATH=/root/.cargo/bin:$PATH rustc --version # remove some 300-400 MB that we don't need for automated builds -rm -rf /root/.rustup/toolchains/nightly-2020-03-19-x86_64-unknown-linux-gnu/share/ +rm -rf /root/.rustup/toolchains/1.43.1-x86_64-unknown-linux-gnu/share diff --git a/ci_scripts/remote_python_packaging.sh b/ci_scripts/remote_python_packaging.sh index c6042ace8..61f2e8c9a 100755 --- a/ci_scripts/remote_python_packaging.sh +++ b/ci_scripts/remote_python_packaging.sh @@ -46,6 +46,6 @@ echo "--- Running $CIRCLE_JOB remotely" ssh -t $SSHTARGET bash "$BUILDDIR/exec_docker_run" mkdir -p workspace -rsync -avz "$SSHTARGET:$BUILDDIR/python/.docker-tox/wheelhouse/*manylinux1*" workspace/wheelhouse/ +rsync -avz "$SSHTARGET:$BUILDDIR/python/.docker-tox/wheelhouse/*manylinux201*" workspace/wheelhouse/ rsync -avz "$SSHTARGET:$BUILDDIR/python/.docker-tox/dist/*" workspace/wheelhouse/ rsync -avz "$SSHTARGET:$BUILDDIR/python/doc/_build/" workspace/py-docs diff --git a/ci_scripts/run_all.sh b/ci_scripts/run_all.sh index 6565ceedb..5a320d139 100755 --- a/ci_scripts/run_all.sh +++ b/ci_scripts/run_all.sh @@ -21,7 +21,8 @@ export DCC_RS_DEV=$(pwd) export PATH=$PATH:/opt/python/cp35-cp35m/bin export PYTHONDONTWRITEBYTECODE=1 pushd /bin -ln -s /opt/python/cp27-cp27m/bin/python2.7 +rm -f python3.5 +ln -s /opt/python/cp35-cp35m/bin/python3.5 ln -s /opt/python/cp36-cp36m/bin/python3.6 ln -s /opt/python/cp37-cp37m/bin/python3.7 ln -s /opt/python/cp38-cp38/bin/python3.8 diff --git a/python/README.rst b/python/README.rst index 077d81ab0..002880dd9 100644 --- a/python/README.rst +++ b/python/README.rst @@ -113,10 +113,10 @@ You may look at `examples `_. .. _`deltachat-core`: https://github.com/deltachat/deltachat-core-rust -Building manylinux1 based wheels -================================ +Building manylinux based wheels +==================================== -Building portable manylinux1 wheels which come with libdeltachat.so +Building portable manylinux wheels which come with libdeltachat.so can be done with docker-tooling. using docker pull / premade images diff --git a/python/examples/echo_and_quit.py b/python/examples/echo_and_quit.py index 367d1fd58..0dd3707fe 100644 --- a/python/examples/echo_and_quit.py +++ b/python/examples/echo_and_quit.py @@ -14,8 +14,11 @@ class EchoPlugin: # unconditionally accept the chat message.accept_sender_contact() addr = message.get_sender_contact().addr - text = message.text - message.chat.send_text("echoing from {}:\n{}".format(addr, text)) + if message.is_system_message(): + message.chat.send_text("echoing system message from {}:\n{}".format(addr, message)) + else: + text = message.text + message.chat.send_text("echoing from {}:\n{}".format(addr, text)) @account_hookimpl def ac_message_delivered(self, message): diff --git a/python/src/deltachat/message.py b/python/src/deltachat/message.py index 706f8c8f5..41bd51276 100644 --- a/python/src/deltachat/message.py +++ b/python/src/deltachat/message.py @@ -28,8 +28,10 @@ class Message(object): def __repr__(self): c = self.get_sender_contact() - return "".format( - self.id, c.id, c.addr, self.is_outgoing(), self.chat.id, self.chat.get_name()) + typ = "outgoing" if self.is_outgoing() else "incoming" + return "".format( + typ, self.is_system_message(), repr(self.text[:10]), + self.id, c.id, c.addr, self.chat.id, self.chat.get_name()) @classmethod def from_db(cls, account, id): @@ -94,7 +96,7 @@ class Message(object): def is_system_message(self): """ return True if this message is a system/info message. """ - return lib.dc_msg_is_info(self._dc_msg) + return bool(lib.dc_msg_is_info(self._dc_msg)) def is_setup_message(self): """ return True if this message is a setup message. """ diff --git a/python/tests/auditwheels.py b/python/tests/auditwheels.py index eb4f5055a..bb8549b1c 100644 --- a/python/tests/auditwheels.py +++ b/python/tests/auditwheels.py @@ -10,4 +10,6 @@ if __name__ == "__main__": for relpath in os.listdir(workspacedir): if relpath.startswith("deltachat"): p = os.path.join(workspacedir, relpath) - subprocess.check_call(["auditwheel", "repair", p, "-w", workspacedir]) + subprocess.check_call( + ["auditwheel", "repair", p, "-w", workspacedir, + "--plat", "manylinux2014_x86_64"]) diff --git a/python/tests/test_account.py b/python/tests/test_account.py index d134bc0a1..af590b866 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -1064,6 +1064,38 @@ class TestOnlineAccount: assert mime.get_all("From") assert mime.get_all("Received") + @pytest.mark.xfail(reason="core emits wrong DC_EVENT_INCOMING_MSG event") + def test_send_mark_seen_clean_incoming_events(self, acfactory, lp, data): + ac1, ac2 = acfactory.get_two_online_accounts() + chat = self.get_chat(ac1, ac2, both_created=True) + + message_queue = queue.Queue() + + class InPlugin: + @account_hookimpl + def ac_incoming_message(self, message): + message_queue.put(message) + + ac1.add_account_plugin(InPlugin()) + + lp.sec("sending one message from ac1 to ac2") + chat.send_text("hello") + + lp.sec("ac2: waiting to receive") + msg = ac2._evtracker.wait_next_incoming_message() + assert msg.text == "hello" + + lp.sec("ac2: mark seen {}".format(msg)) + msg.mark_seen() + + lp.sec("ac2: send echo message") + msg.chat.send_text("world") + + lp.sec("ac1: waiting for echo message") + incoming = message_queue.get(timeout=10) + assert incoming.text == "world" + assert msg.is_in_seen() + def test_send_and_receive_image(self, acfactory, lp, data): ac1, ac2 = acfactory.get_two_online_accounts() chat = self.get_chat(ac1, ac2) From 72d4da00950ec76665c8acb453d8c82413c18a51 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Sun, 24 May 2020 00:06:39 +0200 Subject: [PATCH 096/118] feat(imap): process incoming messages in bulk --- src/imap/mod.rs | 233 +++++++++++++++++++++++++++++++----------------- 1 file changed, 151 insertions(+), 82 deletions(-) diff --git a/src/imap/mod.rs b/src/imap/mod.rs index d0e98cb36..f235a4f21 100644 --- a/src/imap/mod.rs +++ b/src/imap/mod.rs @@ -578,10 +578,6 @@ impl Imap { .await?; let mut read_cnt: usize = 0; - let mut read_errors = 0; - - // prefetch info from all unfetched mails - let mut new_last_seen_uid = last_seen_uid; if self.session.is_none() { return Err(Error::NoConnection); @@ -606,84 +602,109 @@ impl Imap { drop(list); msgs.sort_unstable_by_key(|msg| msg.uid.unwrap_or_default()); + let msgs: Vec<_> = msgs + .into_iter() + .filter(|msg| { + let cur_uid = msg.uid.unwrap_or_default(); + if cur_uid <= last_seen_uid { + // If the mailbox is not empty, results always include + // at least one UID, even if last_seen_uid+1 is past + // the last UID in the mailbox. It happens because + // uid+1:* is interpreted the same way as *:uid+1. + // See https://tools.ietf.org/html/rfc3501#page-61 for + // standard reference. Therefore, sometimes we receive + // already seen messages and have to filter them out. + info!( + context, + "fetch_new_messages: ignoring uid {}, last seen was {}", + cur_uid, + last_seen_uid + ); + false + } else { + true + } + }) + .collect(); + + read_cnt += msgs.len(); + + let mut read_errors = 0; + + let mut uids = Vec::with_capacity(msgs.len()); + let mut new_last_seen_uid = None; for fetch in msgs.into_iter() { + let folder: &str = folder.as_ref(); + let cur_uid = fetch.uid.unwrap_or_default(); - if cur_uid <= last_seen_uid { - // If the mailbox is not empty, results always include - // at least one UID, even if last_seen_uid+1 is past - // the last UID in the mailbox. It happens because - // uid+1:* is interpreted the same way as *:uid+1. - // See https://tools.ietf.org/html/rfc3501#page-61 for - // standard reference. Therefore, sometimes we receive - // already seen messages and have to filter them out. - info!( - context, - "fetch_new_messages: ignoring uid {}, last seen was {}", cur_uid, last_seen_uid - ); - continue; - } - read_cnt += 1; - let headers = get_fetch_headers(&fetch)?; + let headers = match get_fetch_headers(&fetch) { + Ok(h) => h, + Err(err) => { + warn!(context, "get_fetch_headers error: {}", err); + read_errors += 1; + continue; + } + }; + let message_id = prefetch_get_message_id(&headers).unwrap_or_default(); - if let Ok(true) = precheck_imf(context, &message_id, folder.as_ref(), cur_uid) - .await - .map_err(|err| { + let skip = match precheck_imf(context, &message_id, folder, cur_uid).await { + Ok(skip) => skip, + Err(err) => { warn!(context, "precheck_imf error: {}", err); - err - }) - { + true + } + }; + + if skip { // we know the message-id already or don't want the message otherwise. info!( context, - "Skipping message {} from \"{}\" by precheck.", - message_id, - folder.as_ref(), + "Skipping message {} from \"{}\" by precheck.", message_id, folder, ); + if read_errors == 0 { + new_last_seen_uid = Some(cur_uid); + } } else { // we do not know the message-id // or the message-id is missing (in this case, we create one in the further process) // or some other error happened - let show = prefetch_should_download(context, &headers, show_emails) - .await - .map_err(|err| { + let show = match prefetch_should_download(context, &headers, show_emails).await { + Ok(show) => show, + Err(err) => { warn!(context, "prefetch_should_download error: {}", err); - err - }) - .unwrap_or(true); + true + } + }; - if !show { + if show { + uids.push(cur_uid); + } else { info!( context, - "Ignoring new message {} from \"{}\".", - message_id, - folder.as_ref(), + "Ignoring new message {} from \"{}\".", message_id, folder, ); - } else { - // check passed, go fetch the rest - if let Err(err) = self.fetch_single_msg(context, &folder, cur_uid).await { - info!( - context, - "Read error for message {} from \"{}\", trying over later: {}.", - message_id, - folder.as_ref(), - err - ); - read_errors += 1; - } } - } - - if read_errors == 0 { - new_last_seen_uid = cur_uid; + if read_errors == 0 { + new_last_seen_uid = Some(cur_uid); + } } } - if new_last_seen_uid > last_seen_uid { + // check passed, go fetch the emails + let (new_last_seen_uid_processed, error_cnt) = + self.fetch_many_msgs(context, &folder, &uids).await; + + let new_last_seen_uid_processed = new_last_seen_uid_processed.unwrap_or_default(); + let new_last_seen_uid = new_last_seen_uid.unwrap_or_default(); + let last_one = new_last_seen_uid.max(new_last_seen_uid_processed); + if last_one > last_seen_uid { self.set_config_last_seen_uid(context, &folder, uid_validity, new_last_seen_uid) .await; } + read_errors += error_cnt; + if read_errors > 0 { warn!( context, @@ -721,25 +742,36 @@ impl Imap { .ok(); } - /// Fetches a single message by server UID. + /// Fetches a list of messages by server UID. + /// The passed in list of uids must be sorted. /// - /// If it succeeds, the message should be treated as received even - /// if no database entries are created. If the function returns an - /// error, the caller should try again later. - async fn fetch_single_msg>( + /// Returns the last uid fetch successfully and an error count. + async fn fetch_many_msgs>( &mut self, context: &Context, folder: S, - server_uid: u32, - ) -> Result<()> { - if !self.is_connected() { - return Err(Error::Other("Not connected".to_string())); + server_uids: &[u32], + ) -> (Option, usize) { + if server_uids.is_empty() { + return (None, 0); } - let set = format!("{}", server_uid); + let set = if server_uids.len() == 1 { + server_uids[0].to_string() + } else { + let first_uid = server_uids[0]; + let last_uid = server_uids[server_uids.len() - 1]; + assert!(first_uid < last_uid, "uids must be sorted"); + format!("{}:{}", first_uid, last_uid) + }; + + if !self.is_connected() { + warn!(context, "Not connected"); + return (None, server_uids.len()); + } let mut msgs = if let Some(ref mut session) = &mut self.session { - match session.uid_fetch(set, BODY_FLAGS).await { + match session.uid_fetch(&set, BODY_FLAGS).await { Ok(msgs) => msgs, Err(err) => { // TODO maybe differentiate between IO and input/parsing problems @@ -747,43 +779,80 @@ impl Imap { self.should_reconnect = true; warn!( context, - "Error on fetching message #{} from folder \"{}\"; error={}.", - server_uid, + "Error on fetching messages #{} from folder \"{}\"; error={}.", + &set, folder.as_ref(), err ); - return Err(Error::FetchFailed(err)); + return (None, server_uids.len()); } } } else { // we could not get a valid imap session, this should be retried self.trigger_reconnect(); - return Err(Error::Other("Could not get IMAP session".to_string())); + warn!(context, "Could not get IMAP session"); + return (None, server_uids.len()); }; - if let Some(Ok(msg)) = msgs.next().await { + let mut read_errors = 0; + let mut last_uid = None; + let mut count = 0; + + let mut jobs = Vec::with_capacity(server_uids.len()); + + while let Some(Ok(msg)) = msgs.next().await { + let server_uid = msg.uid.unwrap_or_default(); + if !server_uids.contains(&server_uid) { + // skip if there are some in between we are not interested in + continue; + } + count += 1; + // XXX put flags into a set and pass them to dc_receive_imf let is_deleted = msg.flags().any(|flag| flag == Flag::Deleted); let is_seen = msg.flags().any(|flag| flag == Flag::Seen); if !is_deleted && msg.body().is_some() { - let body = msg.body().unwrap_or_default(); - if let Err(err) = - dc_receive_imf(context, &body, folder.as_ref(), server_uid, is_seen).await - { - return Err(Error::Other(format!("dc_receive_imf error: {}", err))); + let folder = folder.as_ref().to_string(); + let context = context.clone(); + let task = async_std::task::spawn(async move { + let body = msg.body().unwrap_or_default(); + + if let Err(err) = + dc_receive_imf(&context, &body, &folder, server_uid, is_seen).await + { + warn!(context, "dc_receive_imf error: {}", err); + read_errors += 1; + None + } else { + Some(server_uid) + } + }); + jobs.push(task); + } + } + + for task in futures::future::join_all(jobs).await { + match task { + Some(uid) => { + last_uid = Some(uid); + } + None => { + read_errors += 1; } } - } else { + } + + if count != server_uids.len() { warn!( context, - "Message #{} does not exist in folder \"{}\".", - server_uid, - folder.as_ref() + "failed to fetch all uids: got {}, requested {}", + count, + server_uids.len() ); } - Ok(()) + (last_uid, read_errors) } pub async fn can_move(&self) -> bool { From a068b82671d6d361e39bc1c56c29772316324f45 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Sun, 24 May 2020 08:10:03 +0200 Subject: [PATCH 097/118] actually this test passes, hum -- but there is a problem i swear --- python/tests/test_account.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python/tests/test_account.py b/python/tests/test_account.py index af590b866..23d9f1e29 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -1064,7 +1064,6 @@ class TestOnlineAccount: assert mime.get_all("From") assert mime.get_all("Received") - @pytest.mark.xfail(reason="core emits wrong DC_EVENT_INCOMING_MSG event") def test_send_mark_seen_clean_incoming_events(self, acfactory, lp, data): ac1, ac2 = acfactory.get_two_online_accounts() chat = self.get_chat(ac1, ac2, both_created=True) From 7f4627356b151fb6da518c22cfabda3bdb28197d Mon Sep 17 00:00:00 2001 From: holger krekel Date: Sun, 24 May 2020 15:22:55 +0200 Subject: [PATCH 098/118] test and fix buggy parsing of incoming message which would show MDNs as empty incoming messages --- python/src/deltachat/events.py | 6 +++++- python/tests/test_account.py | 12 +++++------- src/dc_receive_imf.rs | 4 ++-- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/python/src/deltachat/events.py b/python/src/deltachat/events.py index d68f83dd3..8ff0ae770 100644 --- a/python/src/deltachat/events.py +++ b/python/src/deltachat/events.py @@ -99,12 +99,16 @@ class FFIEventTracker: break def get_matching(self, event_name_regex, check_error=True, timeout=None): + for ev in self.yield_matching(event_name_regex, check_error, timeout): + return ev + + def yield_matching(self, event_name_regex, check_error=True, timeout=None): self.account.log("-- waiting for event with regex: {} --".format(event_name_regex)) rex = re.compile("(?:{}).*".format(event_name_regex)) while 1: ev = self.get(timeout=timeout, check_error=check_error) if rex.match(ev.name): - return ev + yield ev def get_info_matching(self, regex): rex = re.compile("(?:{}).*".format(regex)) diff --git a/python/tests/test_account.py b/python/tests/test_account.py index 23d9f1e29..9ea912ed4 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -1087,13 +1087,11 @@ class TestOnlineAccount: lp.sec("ac2: mark seen {}".format(msg)) msg.mark_seen() - lp.sec("ac2: send echo message") - msg.chat.send_text("world") - - lp.sec("ac1: waiting for echo message") - incoming = message_queue.get(timeout=10) - assert incoming.text == "world" - assert msg.is_in_seen() + for ev in ac1._evtracker.yield_matching(""): + if ev.name == "DC_EVENT_INCOMING_MSG": + pytest.fail("MDN arrived as regular incoming message") + elif ev.name == "DC_EVENT_MSG_READ": + break def test_send_and_receive_image(self, acfactory, lp, data): ac1, ac2 = acfactory.get_two_online_accounts() diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index 813ed6732..eb303cadd 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -656,10 +656,10 @@ async fn add_parts( .sql .with_conn(move |mut conn| { let mut ids = Vec::with_capacity(parts.len()); + let mut is_hidden = is_hidden; + for part in &mut parts { let mut txt_raw = "".to_string(); - let mut is_hidden = is_hidden; - let mut stmt = conn.prepare_cached( "INSERT INTO msgs \ (rfc724_mid, server_folder, server_uid, chat_id, from_id, to_id, timestamp, \ From fa3ee4205dca9cef0cd37d3c06a74ad1b4829f77 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Sun, 24 May 2020 15:36:20 +0200 Subject: [PATCH 099/118] refactor some python infra, and don't do shutdown on __del__, it's not prepared for running during teardown --- python/src/deltachat/account.py | 4 ++-- python/src/deltachat/events.py | 38 ++++++++++++++++----------------- python/tests/test_account.py | 2 +- 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index f09f23615..487c9b1c3 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -67,8 +67,8 @@ class Account(object): """ re-enable logging. """ self._logging = True - def __del__(self): - self.shutdown() + # def __del__(self): + # self.shutdown() def log(self, msg): if self._logging: diff --git a/python/src/deltachat/events.py b/python/src/deltachat/events.py index 8ff0ae770..43999fe0e 100644 --- a/python/src/deltachat/events.py +++ b/python/src/deltachat/events.py @@ -77,9 +77,26 @@ class FFIEventTracker: timeout = timeout if timeout is not None else self._timeout ev = self._event_queue.get(timeout=timeout) if check_error and ev.name == "DC_EVENT_ERROR": - raise ValueError(str(ev)) + raise ValueError("unexpected event: {}".format(ev)) return ev + def iter_events(self, timeout=None, check_error=True): + while 1: + yield self.get(timeout=timeout, check_error=check_error) + + def get_matching(self, event_name_regex, check_error=True, timeout=None): + rex = re.compile("(?:{}).*".format(event_name_regex)) + for ev in self.iter_events(timeout=timeout, check_error=check_error): + if rex.match(ev.name): + return ev + + def get_info_matching(self, regex): + rex = re.compile("(?:{}).*".format(regex)) + while 1: + ev = self.get_matching("DC_EVENT_INFO") + if rex.match(ev.data2): + return ev + def ensure_event_not_queued(self, event_name_regex): __tracebackhide__ = True rex = re.compile("(?:{}).*".format(event_name_regex)) @@ -98,25 +115,6 @@ class FFIEventTracker: print("** SECUREJOINT-INVITER PROGRESS {}".format(target), self.account) break - def get_matching(self, event_name_regex, check_error=True, timeout=None): - for ev in self.yield_matching(event_name_regex, check_error, timeout): - return ev - - def yield_matching(self, event_name_regex, check_error=True, timeout=None): - self.account.log("-- waiting for event with regex: {} --".format(event_name_regex)) - rex = re.compile("(?:{}).*".format(event_name_regex)) - while 1: - ev = self.get(timeout=timeout, check_error=check_error) - if rex.match(ev.name): - yield ev - - def get_info_matching(self, regex): - rex = re.compile("(?:{}).*".format(regex)) - while 1: - ev = self.get_matching("DC_EVENT_INFO") - if rex.match(ev.data2): - return ev - def wait_next_incoming_message(self): """ wait for and return next incoming message. """ ev = self.get_matching("DC_EVENT_INCOMING_MSG") diff --git a/python/tests/test_account.py b/python/tests/test_account.py index 9ea912ed4..aa124ab74 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -1087,7 +1087,7 @@ class TestOnlineAccount: lp.sec("ac2: mark seen {}".format(msg)) msg.mark_seen() - for ev in ac1._evtracker.yield_matching(""): + for ev in ac1._evtracker.iter_events(): if ev.name == "DC_EVENT_INCOMING_MSG": pytest.fail("MDN arrived as regular incoming message") elif ev.name == "DC_EVENT_MSG_READ": From 0760bfaf7b5eab2df25d303894dfec6b25d18ee3 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Sun, 24 May 2020 17:20:04 +0200 Subject: [PATCH 100/118] use patched version of smol to avoid nix dependency --- Cargo.lock | 35 +++++++++++++++++------------------ Cargo.toml | 2 ++ 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 17eb26e3b..b58facc12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -913,7 +913,7 @@ dependencies = [ "lazy_static", "rand 0.7.3", "time", - "version_check 0.9.1", + "version_check 0.9.2", ] [[package]] @@ -1029,7 +1029,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d371106cc88ffdfb1eabd7111e432da544f16f3e2d7bf1dfe8bf575f1df045cd" dependencies = [ "backtrace", - "version_check 0.9.1", + "version_check 0.9.2", ] [[package]] @@ -1537,9 +1537,9 @@ checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" [[package]] name = "kv-log-macro" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2d3beed37e5483887d81eb39de6de03a8346531410e1306ca48a9a89bd3a51" +checksum = "4ff57d6d215f7ca7eb35a9a64d656ba4d9d2bef114d741dc08048e75e2f5d418" dependencies = [ "log", ] @@ -1794,7 +1794,7 @@ checksum = "0b471253da97532da4b61552249c521e01e736071f71c1a4f7ebbfbf0a06aad6" dependencies = [ "lexical-core", "memchr", - "version_check 0.9.1", + "version_check 0.9.2", ] [[package]] @@ -1936,9 +1936,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.56" +version = "0.9.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f02309a7f127000ed50594f0b50ecc69e7c654e16d41b4e8156d1b3df8e0b52e" +checksum = "7410fef80af8ac071d4f63755c0ab89ac3df0fd1ea91f1d1f37cf5cec4395990" dependencies = [ "autocfg 1.0.0", "cc", @@ -2158,9 +2158,9 @@ checksum = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694" [[package]] name = "proc-macro2" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a50b9351bfa8d65a7d93ce712dc63d2fd15ddbf2c36990fc7cac344859c04f" +checksum = "1502d12e458c49a4c9cbff560d0fe0060c252bc29799ed94ca2ed4bb665a0101" dependencies = [ "unicode-xid 0.2.0", ] @@ -2750,9 +2750,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27044adfd2e1f077f649f59deb9490d3941d674002f7d062870a60ebe9bd47a0" +checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" dependencies = [ "block-buffer", "digest", @@ -2804,8 +2804,7 @@ checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" [[package]] name = "smol" version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686c634ad1873fffef6aed20f180eede424fbf3bb31802394c90fd7335a661b7" +source = "git+https://github.com/dignifiedquire/smol?branch=isolate-nix#09727d70f2349a3991162abd90e751838e1617b5" dependencies = [ "async-task", "crossbeam", @@ -3086,7 +3085,7 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" dependencies = [ - "version_check 0.9.1", + "version_check 0.9.2", ] [[package]] @@ -3171,9 +3170,9 @@ checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" [[package]] name = "version_check" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" [[package]] name = "void" @@ -3285,9 +3284,9 @@ dependencies = [ [[package]] name = "wepoll-binding" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7511014d92edaa2cba607fd6dc62974915bebc3e16f91f911a4bb240b4a3a68c" +checksum = "374fff4ff9701ff8b6ad0d14bacd3156c44063632d8c136186ff5967d48999a7" dependencies = [ "bitflags", "wepoll-sys", diff --git a/Cargo.toml b/Cargo.toml index f77843b51..7a7c41cf7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -96,3 +96,5 @@ repl = ["internals", "rustyline", "log", "pretty_env_logger", "ansi_term"] vendored = ["async-native-tls/vendored", "async-smtp/native-tls-vendored"] nightly = ["pgp/nightly"] +[patch.crates-io] +smol = { git = "https://github.com/dignifiedquire/smol", branch = "isolate-nix" } \ No newline at end of file From 811655bc989fc37300f5608167ffa16d0f186257 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Sun, 24 May 2020 19:06:22 +0200 Subject: [PATCH 101/118] update deps, for real --- Cargo.lock | 23 +++++------------------ Cargo.toml | 4 ++-- 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b58facc12..195b42032 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -136,9 +136,9 @@ dependencies = [ [[package]] name = "async-imap" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dacff85e0a4ef94b196344a715bfb655d1ec806693d7a566569154b5ea74284" +checksum = "8113a1ae7ed80ce764f7474f4323b66f945367c2195d046cfa5efdde30fca04c" dependencies = [ "async-native-tls", "async-std", @@ -1757,19 +1757,6 @@ dependencies = [ "void", ] -[[package]] -name = "nix" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" -dependencies = [ - "bitflags", - "cc", - "cfg-if", - "libc", - "void", -] - [[package]] name = "nodrop" version = "0.1.14" @@ -2579,7 +2566,7 @@ dependencies = [ "libc", "log", "memchr", - "nix 0.13.1", + "nix", "unicode-segmentation", "unicode-width", "utf8parse", @@ -2804,13 +2791,13 @@ checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" [[package]] name = "smol" version = "0.1.10" -source = "git+https://github.com/dignifiedquire/smol?branch=isolate-nix#09727d70f2349a3991162abd90e751838e1617b5" +source = "git+https://github.com/dignifiedquire/smol-1?branch=isolate-nix#8b35e961d82e4543feadec86828500092b5e1938" dependencies = [ "async-task", "crossbeam", "futures-io", "futures-util", - "nix 0.17.0", + "libc", "once_cell", "piper", "scoped-tls-hkt", diff --git a/Cargo.toml b/Cargo.toml index 7a7c41cf7..d71d37980 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ num-traits = "0.2.6" async-smtp = { version = "0.3" } email = { git = "https://github.com/deltachat/rust-email", branch = "master" } lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" } -async-imap = "0.3.0" +async-imap = "0.3.1" async-native-tls = { version = "0.3.3" } async-std = { version = "1.6.0", features = ["unstable"] } base64 = "0.11" @@ -97,4 +97,4 @@ vendored = ["async-native-tls/vendored", "async-smtp/native-tls-vendored"] nightly = ["pgp/nightly"] [patch.crates-io] -smol = { git = "https://github.com/dignifiedquire/smol", branch = "isolate-nix" } \ No newline at end of file +smol = { git = "https://github.com/dignifiedquire/smol-1", branch = "isolate-nix" } \ No newline at end of file From d9de33820fdfb550e2692c6fbd452ccfe45f3449 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Sat, 16 May 2020 20:31:09 +0200 Subject: [PATCH 102/118] add a stress test --- python/src/deltachat/account.py | 3 +- python/src/deltachat/testplugin.py | 15 +++- python/tests/stress_test_db.py | 135 +++++++++++++++++++++++++++++ 3 files changed, 147 insertions(+), 6 deletions(-) create mode 100644 python/tests/stress_test_db.py diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index 487c9b1c3..34f2e63ba 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -52,11 +52,10 @@ class Account(object): if self._dc_context == ffi.NULL: raise ValueError("Could not dc_context_new: {} {}".format(os_name, db_path)) - hook = hookspec.Global._get_plugin_manager().hook - self._shutdown_event = Event() self._event_thread = EventThread(self) self._configkeys = self.get_config("sys.config_keys").split() + hook = hookspec.Global._get_plugin_manager().hook hook.dc_account_init(account=self) def disable_logging(self): diff --git a/python/src/deltachat/testplugin.py b/python/src/deltachat/testplugin.py index a785d9cf0..8a1939b08 100644 --- a/python/src/deltachat/testplugin.py +++ b/python/src/deltachat/testplugin.py @@ -15,7 +15,6 @@ import requests from . import Account, const from .capi import lib from .events import FFIEventLogger, FFIEventTracker -from _pytest.monkeypatch import MonkeyPatch from _pytest._code import Source import deltachat @@ -101,17 +100,16 @@ def pytest_report_header(config, startdir): summary = [] t = tempfile.mktemp() - m = MonkeyPatch() try: ac = Account(t) info = ac.get_info() ac.shutdown() finally: - m.undo() os.remove(t) - summary.extend(['Deltachat core={} sqlite={}'.format( + summary.extend(['Deltachat core={} sqlite={} journal_mode={}'.format( info['deltachat_core_version'], info['sqlite_version'], + info['journal_mode'], )]) cfg = config.option.liveconfig @@ -232,6 +230,7 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, data): def make_account(self, path, logid, quiet=False): ac = Account(path, logging=self._logging) ac._evtracker = ac.add_account_plugin(FFIEventTracker(ac)) + ac.addr = ac.get_self_contact().addr if not quiet: ac.add_account_plugin(FFIEventLogger(ac, logid=logid)) self._accounts.append(ac) @@ -321,6 +320,14 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, data): ac2.start_io() return ac1, ac2 + def get_many_online_accounts(self, num, move=True, quiet=True): + accounts = [self.get_online_configuring_account(move=move, quiet=quiet) + for i in range(num)] + for acc in accounts: + acc._configtracker.wait_finish() + acc.start_io() + return accounts + def clone_online_account(self, account, pre_generated_key=True): self.live_count += 1 tmpdb = tmpdir.join("livedb%d" % self.live_count) diff --git a/python/tests/stress_test_db.py b/python/tests/stress_test_db.py new file mode 100644 index 000000000..ab5813218 --- /dev/null +++ b/python/tests/stress_test_db.py @@ -0,0 +1,135 @@ + +import time +import threading +import pytest +import os +from queue import Queue, Empty + +import deltachat + + +def test_db_busy_error(acfactory, tmpdir): + starttime = time.time() + log_lock = threading.RLock() + + def log(string): + with log_lock: + print("%3.2f %s" % (time.time() - starttime, string)) + + # make a number of accounts + accounts = acfactory.get_many_online_accounts(3, quiet=True) + log("created %s accounts" % len(accounts)) + + # put a bigfile into each account + for acc in accounts: + acc.bigfile = os.path.join(acc.get_blobdir(), "bigfile") + with open(acc.bigfile, "wb") as f: + f.write(b"01234567890"*1000_000) + log("created %s bigfiles" % len(accounts)) + + contact_addrs = [acc.get_self_contact().addr for acc in accounts] + chat = accounts[0].create_group_chat("stress-group") + for addr in contact_addrs[1:]: + chat.add_contact(chat.account.create_contact(addr)) + + # setup auto-responder bots which report back failures/actions + report_queue = Queue() + + def report_func(replier, report_type, *report_args): + report_queue.put((replier, report_type, report_args)) + + # each replier receives all events and sends report events to receive_queue + repliers = [] + for acc in accounts: + replier = AutoReplier(acc, log=log, num_send=500, num_bigfiles=5, report_func=report_func) + acc.add_account_plugin(replier) + repliers.append(replier) + + # kick off message sending + # after which repliers will reply to each other + chat.send_text("hello") + + alive_count = len(accounts) + while alive_count > 0: + try: + replier, report_type, report_args = report_queue.get(timeout=10) + except Empty: + log("timeout waiting for next event") + pytest.fail("timeout exceeded") + if report_type == ReportType.exit: + replier.log("EXIT".format(alive_count)) + elif report_type == ReportType.ffi_error: + replier.log("ERROR: {}".format(addr, report_args[0])) + elif report_type == ReportType.message_echo: + continue + else: + raise ValueError("{} unknown report type {}, args={}".format( + addr, report_type, report_args + )) + alive_count -= 1 + replier.log("shutting down") + replier.account.shutdown() + replier.log("shut down complete, remaining={}".format(alive_count)) + + +class ReportType: + exit = "exit" + ffi_error = "ffi-error" + message_echo = "message-echo" + + +class AutoReplier: + def __init__(self, account, log, num_send, num_bigfiles, report_func): + self.account = account + self._log = log + self.report_func = report_func + self.num_send = num_send + self.num_bigfiles = num_bigfiles + self.current_sent = 0 + self.addr = self.account.get_self_contact().addr + + self._thread = threading.Thread( + name="Stats{}".format(self.account), + target=self.thread_stats + ) + self._thread.setDaemon(True) + self._thread.start() + + def log(self, message): + self._log("{} {}".format(self.addr, message)) + + def thread_stats(self): + # XXX later use, for now we just quit + return + while 1: + time.sleep(1.0) + break + + @deltachat.account_hookimpl + def ac_incoming_message(self, message): + if self.current_sent >= self.num_send: + self.report_func(self, ReportType.exit) + return + message.accept_sender_contact() + message.mark_seen() + self.log("incoming message: {}".format(message)) + + self.current_sent += 1 + # we are still alive, let's send a reply + if self.num_bigfiles and self.current_sent % (self.num_send / self.num_bigfiles) == 0: + message.chat.send_text("send big file as reply to: {}".format(message.text)) + msg = message.chat.send_file(self.account.bigfile) + else: + msg = message.chat.send_text("got message id {}, small text reply".format(message.id)) + assert msg.text + self.log("message-sent: {}".format(msg)) + self.report_func(self, ReportType.message_echo) + if self.current_sent >= self.num_send: + self.report_func(self, ReportType.exit) + return + + @deltachat.account_hookimpl + def ac_process_ffi_event(self, ffi_event): + self.log(ffi_event) + if ffi_event.name == "DC_EVENT_ERROR": + self.report_func(self, ReportType.ffi_error, ffi_event) From 9442df0cf81d1b093701783180a81ce1361ef2c5 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Sun, 24 May 2020 21:24:13 +0200 Subject: [PATCH 103/118] fix: restore logic to original in configure --- src/configure/mod.rs | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/configure/mod.rs b/src/configure/mod.rs index 94b58ab3c..602c316ce 100644 --- a/src/configure/mod.rs +++ b/src/configure/mod.rs @@ -4,7 +4,7 @@ mod auto_mozilla; mod auto_outlook; mod read_url; -use anyhow::{bail, ensure, Result}; +use anyhow::{bail, ensure, format_err, Result}; use async_std::prelude::*; use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; @@ -119,6 +119,23 @@ impl Context { imap.disconnect(self).await; } + // remember the entered parameters on success + // and restore to last-entered on failure. + // this way, the parameters visible to the ui are always in-sync with the current configuration. + if success { + LoginParam::from_database(self, "") + .await + .save_to_database(self, "configured_raw_") + .await + .ok(); + } else { + LoginParam::from_database(self, "configured_raw_") + .await + .save_to_database(self, "") + .await + .ok(); + } + if let Some(provider) = provider::get_provider_info(¶m.addr) { if !was_configured_before { if let Some(config_defaults) = &provider.config_defaults { @@ -141,27 +158,12 @@ impl Context { } } - // remember the entered parameters on success - // and restore to last-entered on failure. - // this way, the parameters visible to the ui are always in-sync with the current configuration. if success { - LoginParam::from_database(self, "") - .await - .save_to_database(self, "configured_raw_") - .await - .ok(); - progress!(self, 1000); Ok(()) } else { - LoginParam::from_database(self, "configured_raw_") - .await - .save_to_database(self, "") - .await - .ok(); - progress!(self, 0); - bail!("Configure failed") + Err(format_err!("Configure failed")) } } } From 00e8f2271a26cba9e49434280787a664b072ccfc Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Sun, 24 May 2020 21:24:21 +0200 Subject: [PATCH 104/118] improve simple example --- Cargo.toml | 1 + examples/simple.rs | 48 ++++++++++++++++++++++++---------------------- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d71d37980..a63cf33b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -82,6 +82,7 @@ members = [ [[example]] name = "simple" path = "examples/simple.rs" +required-features = ["repl"] [[example]] name = "repl" diff --git a/examples/simple.rs b/examples/simple.rs index 3d8dc6cba..cdfeeea89 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -1,6 +1,3 @@ -extern crate deltachat; - -use std::time; use tempfile::tempdir; use deltachat::chat; @@ -11,32 +8,38 @@ use deltachat::context::*; use deltachat::Event; fn cb(event: Event) { - print!("[{:?}]", event); - match event { Event::ConfigureProgress(progress) => { - println!(" progress: {}", progress); + log::info!("progress: {}", progress); } - Event::Info(msg) | Event::Warning(msg) | Event::Error(msg) | Event::ErrorNetwork(msg) => { - println!(" {}", msg); + Event::Info(msg) => { + log::info!("{}", msg); } - _ => { - println!(); + Event::Warning(msg) => { + log::warn!("{}", msg); + } + Event::Error(msg) | Event::ErrorNetwork(msg) => { + log::error!("{}", msg); + } + event => { + log::info!("{:?}", event); } } } +/// Run with `RUST_LOG=simple=info cargo run --release --example simple --features repl -- email pw`. #[async_std::main] async fn main() { + pretty_env_logger::try_init_timed().ok(); + let dir = tempdir().unwrap(); let dbfile = dir.path().join("db.sqlite"); - println!("creating database {:?}", dbfile); + log::info!("creating database {:?}", dbfile); let ctx = Context::new("FakeOs".into(), dbfile.into()) .await .expect("Failed to create context"); let info = ctx.get_info().await; - let duration = time::Duration::from_millis(4000); - println!("info: {:#?}", info); + log::info!("info: {:#?}", info); let events = ctx.get_event_emitter(); let events_spawn = async_std::task::spawn(async move { @@ -45,7 +48,7 @@ async fn main() { } }); - println!("configuring"); + log::info!("configuring"); let args = std::env::args().collect::>(); assert_eq!(args.len(), 3, "requires email password"); let email = args[1].clone(); @@ -59,9 +62,9 @@ async fn main() { ctx.configure().await.unwrap(); - println!("------ RUN ------"); - ctx.clone().start_io().await; - println!("--- SENDING A MESSAGE ---"); + log::info!("------ RUN ------"); + ctx.start_io().await; + log::info!("--- SENDING A MESSAGE ---"); let contact_id = Contact::create(&ctx, "dignifiedquire", "dignifiedquire@gmail.com") .await @@ -74,20 +77,19 @@ async fn main() { .unwrap(); } - println!("fetching chats.."); + log::info!("fetching chats.."); let chats = Chatlist::try_load(&ctx, 0, None, None).await.unwrap(); for i in 0..chats.len() { let summary = chats.get_summary(&ctx, 0, None).await; let text1 = summary.get_text1(); let text2 = summary.get_text2(); - println!("chat: {} - {:?} - {:?}", i, text1, text2,); + log::info!("chat: {} - {:?} - {:?}", i, text1, text2,); } - async_std::task::sleep(duration).await; - - println!("stopping"); + log::info!("stopping"); ctx.stop_io().await; - println!("closing"); + log::info!("closing"); + drop(ctx); events_spawn.await; } From 477e689c74f1f822e1015b5f7324025e54faaa9f Mon Sep 17 00:00:00 2001 From: holger krekel Date: Sun, 24 May 2020 22:11:14 +0200 Subject: [PATCH 105/118] fix python lint --- python/tests/stress_test_db.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/tests/stress_test_db.py b/python/tests/stress_test_db.py index ab5813218..178ddcba6 100644 --- a/python/tests/stress_test_db.py +++ b/python/tests/stress_test_db.py @@ -57,9 +57,9 @@ def test_db_busy_error(acfactory, tmpdir): log("timeout waiting for next event") pytest.fail("timeout exceeded") if report_type == ReportType.exit: - replier.log("EXIT".format(alive_count)) + replier.log("EXIT") elif report_type == ReportType.ffi_error: - replier.log("ERROR: {}".format(addr, report_args[0])) + replier.log("ERROR: {}".format(report_args[0])) elif report_type == ReportType.message_echo: continue else: From 2adeadfd73e5d24aa1ac34aa32fe1086a379e163 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Mon, 25 May 2020 00:17:01 +0200 Subject: [PATCH 106/118] fix: avoid blocking on expensive pgp operations --- Cargo.lock | 1 + Cargo.toml | 3 +- src/e2ee.rs | 65 ++++++----- src/imex.rs | 26 ++--- src/key.rs | 23 ++-- src/keyring.rs | 49 ++++---- src/mimefactory.rs | 4 +- src/peerstate.rs | 15 ++- src/pgp.rs | 272 ++++++++++++++++++++++++--------------------- 9 files changed, 241 insertions(+), 217 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 195b42032..3d00952a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -784,6 +784,7 @@ dependencies = [ "serde_json", "sha2", "smallvec", + "smol", "stop-token", "strum", "strum_macros", diff --git a/Cargo.toml b/Cargo.toml index a63cf33b9..c61569fe3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,6 +72,7 @@ pretty_assertions = "0.6.1" pretty_env_logger = "0.3.0" proptest = "0.9.4" async-std = { version = "1.6.0", features = ["unstable", "attributes"] } +smol = "0.1.10" [workspace] members = [ @@ -98,4 +99,4 @@ vendored = ["async-native-tls/vendored", "async-smtp/native-tls-vendored"] nightly = ["pgp/nightly"] [patch.crates-io] -smol = { git = "https://github.com/dignifiedquire/smol-1", branch = "isolate-nix" } \ No newline at end of file +smol = { git = "https://github.com/dignifiedquire/smol-1", branch = "isolate-nix" } diff --git a/src/e2ee.rs b/src/e2ee.rs index 899f1df23..bb3f985d3 100644 --- a/src/e2ee.rs +++ b/src/e2ee.rs @@ -87,30 +87,30 @@ impl EncryptHelper { /// Tries to encrypt the passed in `mail`. pub async fn encrypt( - &mut self, + self, context: &Context, min_verified: PeerstateVerifiedStatus, mail_to_encrypt: lettre_email::PartBuilder, - peerstates: &[(Option>, &str)], + peerstates: Vec<(Option>, &str)>, ) -> Result { let mut keyring = Keyring::default(); for (peerstate, addr) in peerstates - .iter() - .filter_map(|(state, addr)| state.as_ref().map(|s| (s, addr))) + .into_iter() + .filter_map(|(state, addr)| state.map(|s| (s, addr))) { - let key = peerstate.peek_key(min_verified).ok_or_else(|| { + let key = peerstate.take_key(min_verified).ok_or_else(|| { format_err!("proper enc-key for {} missing, cannot encrypt", addr) })?; - keyring.add_ref(key); + keyring.add(key); } - let public_key = Key::from(self.public_key.clone()); - keyring.add_ref(&public_key); + let public_key = Key::from(self.public_key); + keyring.add(public_key); let sign_key = Key::from(SignedSecretKey::load_self(context).await?); let raw_message = mail_to_encrypt.build().as_string().into_bytes(); - let ctext = pgp::pk_encrypt(&raw_message, &keyring, Some(&sign_key))?; + let ctext = pgp::pk_encrypt(&raw_message, keyring, Some(sign_key)).await?; Ok(ctext) } @@ -151,39 +151,38 @@ pub async fn try_decrypt( } /* possibly perform decryption */ - let mut private_keyring = Keyring::default(); let mut public_keyring_for_validate = Keyring::default(); let mut out_mail = None; let mut signatures = HashSet::default(); let self_addr = context.get_config(Config::ConfiguredAddr).await; if let Some(self_addr) = self_addr { - if private_keyring - .load_self_private_for_decrypting(context, self_addr, &context.sql) - .await + if let Ok(private_keyring) = + Keyring::load_self_private_for_decrypting(context, self_addr).await { if peerstate.as_ref().map(|p| p.last_seen).unwrap_or_else(|| 0) == 0 { peerstate = Peerstate::from_addr(&context, &from).await; } - if let Some(ref peerstate) = peerstate { + if let Some(peerstate) = peerstate { if peerstate.degrade_event.is_some() { handle_degrade_event(context, &peerstate).await?; } - if let Some(ref key) = peerstate.gossip_key { - public_keyring_for_validate.add_ref(key); + if let Some(key) = peerstate.gossip_key { + public_keyring_for_validate.add(key); } - if let Some(ref key) = peerstate.public_key { - public_keyring_for_validate.add_ref(key); + if let Some(key) = peerstate.public_key { + public_keyring_for_validate.add(key); } } out_mail = decrypt_if_autocrypt_message( context, mail, - &private_keyring, - &public_keyring_for_validate, + private_keyring, + public_keyring_for_validate, &mut signatures, - )?; + ) + .await?; } } Ok((out_mail, signatures)) @@ -216,11 +215,11 @@ fn get_autocrypt_mime<'a, 'b>(mail: &'a ParsedMail<'b>) -> Result<&'a ParsedMail Ok(&mail.subparts[1]) } -fn decrypt_if_autocrypt_message<'a>( +async fn decrypt_if_autocrypt_message<'a>( context: &Context, mail: &ParsedMail<'a>, - private_keyring: &Keyring, - public_keyring_for_validate: &Keyring, + private_keyring: Keyring, + public_keyring_for_validate: Keyring, ret_valid_signatures: &mut HashSet, ) -> Result>> { // The returned bool is true if we detected an Autocrypt-encrypted @@ -240,20 +239,19 @@ fn decrypt_if_autocrypt_message<'a>( info!(context, "Detected Autocrypt-mime message"); decrypt_part( - context, encrypted_data_part, private_keyring, public_keyring_for_validate, ret_valid_signatures, ) + .await } /// Returns Ok(None) if nothing encrypted was found. -fn decrypt_part( - _context: &Context, +async fn decrypt_part( mail: &ParsedMail<'_>, - private_keyring: &Keyring, - public_keyring_for_validate: &Keyring, + private_keyring: Keyring, + public_keyring_for_validate: Keyring, ret_valid_signatures: &mut HashSet, ) -> Result>> { let data = mail.get_body_raw()?; @@ -263,11 +261,12 @@ fn decrypt_part( ensure!(ret_valid_signatures.is_empty(), "corrupt signatures"); let plain = pgp::pk_decrypt( - &data, - &private_keyring, - &public_keyring_for_validate, + data, + private_keyring, + public_keyring_for_validate, Some(ret_valid_signatures), - )?; + ) + .await?; ensure!(!ret_valid_signatures.is_empty(), "no valid signatures"); return Ok(Some(plain)); diff --git a/src/imex.rs b/src/imex.rs index 2c443e846..563b6aa68 100644 --- a/src/imex.rs +++ b/src/imex.rs @@ -187,7 +187,7 @@ pub async fn render_setup_file(context: &Context, passphrase: &str) -> Result Some(("Autocrypt-Prefer-Encrypt", "mutual")), }; let private_key_asc = private_key.to_asc(ac_headers); - let encr = pgp::symm_encrypt(&passphrase, private_key_asc.as_bytes())?; + let encr = pgp::symm_encrypt(&passphrase, private_key_asc.as_bytes()).await?; let replacement = format!( concat!( @@ -274,7 +274,7 @@ pub async fn continue_key_transfer( if let Some(filename) = msg.get_file(context) { let file = dc_open_file_std(context, filename)?; let sc = normalize_setup_code(setup_code); - let armored_key = decrypt_setup_file(context, &sc, file)?; + let armored_key = decrypt_setup_file(&sc, file).await?; set_self_key(context, &armored_key, true, true).await?; maybe_add_bcc_self_device_msg(context).await?; @@ -345,12 +345,11 @@ async fn set_self_key( Ok(()) } -fn decrypt_setup_file( - _context: &Context, +async fn decrypt_setup_file( passphrase: &str, file: T, ) -> Result { - let plain_bytes = pgp::symm_decrypt(passphrase, file)?; + let plain_bytes = pgp::symm_decrypt(passphrase, file).await?; let plain_text = std::string::String::from_utf8(plain_bytes)?; Ok(plain_text) @@ -713,7 +712,7 @@ async fn export_self_keys(context: &Context, dir: impl AsRef) -> Result<() for (id, public_key, private_key, is_default) in keys { let id = Some(id).filter(|_| is_default != 0); - if let Some(key) = public_key { + if let Ok(key) = public_key { if export_key_to_asc_file(context, &dir, id, &key) .await .is_err() @@ -723,7 +722,7 @@ async fn export_self_keys(context: &Context, dir: impl AsRef) -> Result<() } else { export_errors += 1; } - if let Some(key) = private_key { + if let Ok(key) = private_key { if export_key_to_asc_file(context, &dir, id, &key) .await .is_err() @@ -852,9 +851,6 @@ mod tests { #[async_std::test] async fn test_split_and_decrypt() { - let ctx = dummy_context().await; - let context = &ctx.ctx; - let buf_1 = S_EM_SETUPFILE.as_bytes().to_vec(); let (typ, headers, base64) = split_armored_data(&buf_1).unwrap(); assert_eq!(typ, BlockType::Message); @@ -864,12 +860,10 @@ mod tests { assert!(!base64.is_empty()); let setup_file = S_EM_SETUPFILE.to_string(); - let decrypted = decrypt_setup_file( - context, - S_EM_SETUPCODE, - std::io::Cursor::new(setup_file.as_bytes()), - ) - .unwrap(); + let decrypted = + decrypt_setup_file(S_EM_SETUPCODE, std::io::Cursor::new(setup_file.as_bytes())) + .await + .unwrap(); let (typ, headers, _base64) = split_armored_data(decrypted.as_bytes()).unwrap(); diff --git a/src/key.rs b/src/key.rs index 28df79dbe..c00f77486 100644 --- a/src/key.rs +++ b/src/key.rs @@ -38,6 +38,8 @@ pub enum Error { NoConfiguredAddr, #[error("Configured address is invalid: {}", _0)] InvalidConfiguredAddr(#[from] InvalidEmailError), + #[error("no data provided")] + Empty, } pub type Result = std::result::Result; @@ -260,22 +262,17 @@ impl Key { !self.is_public() } - pub fn from_slice(bytes: &[u8], key_type: KeyType) -> Option { + pub fn from_slice(bytes: &[u8], key_type: KeyType) -> Result { if bytes.is_empty() { - return None; + return Err(Error::Empty); } - let res: std::result::Result = match key_type { - KeyType::Public => SignedPublicKey::from_bytes(Cursor::new(bytes)).map(Into::into), - KeyType::Private => SignedSecretKey::from_bytes(Cursor::new(bytes)).map(Into::into), + + let res = match key_type { + KeyType::Public => SignedPublicKey::from_bytes(Cursor::new(bytes))?.into(), + KeyType::Private => SignedSecretKey::from_bytes(Cursor::new(bytes))?.into(), }; - match res { - Ok(key) => Some(key), - Err(err) => { - eprintln!("Invalid key bytes: {:?}", err); - None - } - } + Ok(res) } pub fn from_armored_string( @@ -625,7 +622,7 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD KeyType::Private }, ); - assert!(bad_key.is_none()); + assert!(bad_key.is_err()); } } diff --git a/src/keyring.rs b/src/keyring.rs index f1da55e13..c962e3fbd 100644 --- a/src/keyring.rs +++ b/src/keyring.rs @@ -1,46 +1,47 @@ -use std::borrow::Cow; +use anyhow::Result; use crate::constants::KeyType; use crate::context::Context; use crate::key::Key; -use crate::sql::Sql; #[derive(Default, Clone, Debug)] -pub struct Keyring<'a> { - keys: Vec>, +pub struct Keyring { + keys: Vec, } -impl<'a> Keyring<'a> { - pub fn add_owned(&mut self, key: Key) { - self.add(Cow::Owned(key)) +impl Keyring { + pub fn add(&mut self, key: Key) { + self.keys.push(key) } - pub fn add_ref(&mut self, key: &'a Key) { - self.add(Cow::Borrowed(key)) + pub fn len(&self) -> usize { + self.keys.len() } - fn add(&mut self, key: Cow<'a, Key>) { - self.keys.push(key); + pub fn is_empty(&self) -> bool { + self.keys.is_empty() } - pub fn keys(&self) -> &[Cow<'a, Key>] { + pub fn keys(&self) -> &[Key] { &self.keys } pub async fn load_self_private_for_decrypting( - &mut self, context: &Context, self_addr: impl AsRef, - sql: &Sql, - ) -> bool { - sql.query_get_value( - context, - "SELECT private_key FROM keypairs ORDER BY addr=? DESC, is_default DESC;", - paramsv![self_addr.as_ref().to_string()], - ) - .await - .and_then(|blob: Vec| Key::from_slice(&blob, KeyType::Private)) - .map(|key| self.add_owned(key)) - .is_some() + ) -> Result { + let blob: Vec = context + .sql + .query_get_value_result( + "SELECT private_key FROM keypairs ORDER BY addr=? DESC, is_default DESC;", + paramsv![self_addr.as_ref().to_string()], + ) + .await? + .unwrap_or_default(); + + let key = async_std::task::spawn_blocking(move || Key::from_slice(&blob, KeyType::Private)) + .await?; + + Ok(Self { keys: vec![key] }) } } diff --git a/src/mimefactory.rs b/src/mimefactory.rs index ceb87d134..e17cf668e 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -463,7 +463,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> { let force_plaintext = self.should_force_plaintext(); let subject_str = self.subject_str().await; let e2ee_guaranteed = self.is_e2ee_guaranteed(); - let mut encrypt_helper = EncryptHelper::new(self.context).await?; + let encrypt_helper = EncryptHelper::new(self.context).await?; let subject = encode_words(&subject_str); @@ -560,7 +560,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> { } let encrypted = encrypt_helper - .encrypt(self.context, min_verified, message, &peerstates) + .encrypt(self.context, min_verified, message, peerstates) .await?; outer_message = outer_message diff --git a/src/peerstate.rs b/src/peerstate.rs index beec1bbf5..fc36020a6 100644 --- a/src/peerstate.rs +++ b/src/peerstate.rs @@ -220,15 +220,15 @@ impl<'a> Peerstate<'a> { res.public_key = row .get(4) .ok() - .and_then(|blob: Vec| Key::from_slice(&blob, KeyType::Public)); + .and_then(|blob: Vec| Key::from_slice(&blob, KeyType::Public).ok()); res.gossip_key = row .get(6) .ok() - .and_then(|blob: Vec| Key::from_slice(&blob, KeyType::Public)); + .and_then(|blob: Vec| Key::from_slice(&blob, KeyType::Public).ok()); res.verified_key = row .get(9) .ok() - .and_then(|blob: Vec| Key::from_slice(&blob, KeyType::Public)); + .and_then(|blob: Vec| Key::from_slice(&blob, KeyType::Public).ok()); Ok(res) }) @@ -367,6 +367,15 @@ impl<'a> Peerstate<'a> { } } + pub fn take_key(mut self, min_verified: PeerstateVerifiedStatus) -> Option { + match min_verified { + PeerstateVerifiedStatus::BidirectVerified => self.verified_key.take(), + PeerstateVerifiedStatus::Unverified => { + self.public_key.take().or_else(|| self.gossip_key.take()) + } + } + } + pub fn peek_key(&self, min_verified: PeerstateVerifiedStatus) -> Option<&Key> { match min_verified { PeerstateVerifiedStatus::BidirectVerified => self.verified_key.as_ref(), diff --git a/src/pgp.rs b/src/pgp.rs index 287c7b0bf..fef20379d 100644 --- a/src/pgp.rs +++ b/src/pgp.rs @@ -238,124 +238,140 @@ fn select_pk_for_encryption(key: &SignedPublicKey) -> Option, + public_keys_for_encryption: Keyring, + private_key_for_signing: Option, ) -> Result { let lit_msg = Message::new_literal_bytes("", plain); - let pkeys: Vec = public_keys_for_encryption - .keys() - .iter() - .filter_map(|key| { - key.as_ref() + + async_std::task::spawn_blocking(move || { + let pkeys: Vec = public_keys_for_encryption + .keys() + .iter() + .filter_map(|key| key.try_into().ok().and_then(select_pk_for_encryption)) + .collect(); + let pkeys_refs: Vec<&SignedPublicKeyOrSubkey> = pkeys.iter().collect(); + + let mut rng = thread_rng(); + + // TODO: measure time + let encrypted_msg = if let Some(ref private_key) = private_key_for_signing { + let skey: &SignedSecretKey = private_key .try_into() - .ok() - .and_then(select_pk_for_encryption) - }) - .collect(); - let pkeys_refs: Vec<&SignedPublicKeyOrSubkey> = pkeys.iter().collect(); + .map_err(|_| format_err!("Invalid private key"))?; - let mut rng = thread_rng(); + lit_msg + .sign(skey, || "".into(), Default::default()) + .and_then(|msg| msg.compress(CompressionAlgorithm::ZLIB)) + .and_then(|msg| msg.encrypt_to_keys(&mut rng, Default::default(), &pkeys_refs)) + } else { + lit_msg.encrypt_to_keys(&mut rng, Default::default(), &pkeys_refs) + }; - // TODO: measure time - let encrypted_msg = if let Some(private_key) = private_key_for_signing { - let skey: &SignedSecretKey = private_key - .try_into() - .map_err(|_| format_err!("Invalid private key"))?; + let msg = encrypted_msg?; + let encoded_msg = msg.to_armored_string(None)?; - lit_msg - .sign(skey, || "".into(), Default::default()) - .and_then(|msg| msg.compress(CompressionAlgorithm::ZLIB)) - .and_then(|msg| msg.encrypt_to_keys(&mut rng, Default::default(), &pkeys_refs)) - } else { - lit_msg.encrypt_to_keys(&mut rng, Default::default(), &pkeys_refs) - }; - - let msg = encrypted_msg?; - let encoded_msg = msg.to_armored_string(None)?; - - Ok(encoded_msg) + Ok(encoded_msg) + }) + .await } #[allow(clippy::implicit_hasher)] -pub fn pk_decrypt( - ctext: &[u8], - private_keys_for_decryption: &Keyring, - public_keys_for_validation: &Keyring, +pub async fn pk_decrypt( + ctext: Vec, + private_keys_for_decryption: Keyring, + public_keys_for_validation: Keyring, ret_signature_fingerprints: Option<&mut HashSet>, ) -> Result> { - let (msg, _) = Message::from_armor_single(Cursor::new(ctext))?; - let skeys: Vec<&SignedSecretKey> = private_keys_for_decryption - .keys() - .iter() - .filter_map(|key| { - let k: &Key = &key; - k.try_into().ok() - }) - .collect(); + let msgs = async_std::task::spawn_blocking(move || { + let cursor = Cursor::new(ctext); + let (msg, _) = Message::from_armor_single(cursor)?; + + let skeys: Vec<&SignedSecretKey> = private_keys_for_decryption + .keys() + .iter() + .filter_map(|key| key.try_into().ok()) + .collect(); + + let (decryptor, _) = msg.decrypt(|| "".into(), || "".into(), &skeys[..])?; + decryptor.collect::>>() + }) + .await?; - let (decryptor, _) = msg.decrypt(|| "".into(), || "".into(), &skeys[..])?; - let msgs = decryptor.collect::>>()?; ensure!(!msgs.is_empty(), "No valid messages found"); - let dec_msg = &msgs[0]; + let content = match msgs[0].get_content()? { + Some(content) => content, + None => bail!("Decrypted message is empty"), + }; if let Some(ret_signature_fingerprints) = ret_signature_fingerprints { - if !public_keys_for_validation.keys().is_empty() { - let pkeys: Vec<&SignedPublicKey> = public_keys_for_validation - .keys() - .iter() - .filter_map(|key| { - let k: &Key = &key; - k.try_into().ok() - }) - .collect(); + if !public_keys_for_validation.is_empty() { + let fingerprints = async_std::task::spawn_blocking(move || { + let dec_msg = &msgs[0]; - for pkey in &pkeys { - if dec_msg.verify(&pkey.primary_key).is_ok() { - let fp = hex::encode_upper(pkey.fingerprint()); - ret_signature_fingerprints.insert(fp); + let pkeys = public_keys_for_validation + .keys() + .iter() + .filter_map(|key| -> Option<&SignedPublicKey> { key.try_into().ok() }); + + let mut fingerprints = Vec::new(); + for pkey in pkeys { + if dec_msg.verify(&pkey.primary_key).is_ok() { + let fp = hex::encode_upper(pkey.fingerprint()); + fingerprints.push(fp); + } } - } + fingerprints + }) + .await; + + ret_signature_fingerprints.extend(fingerprints); } } - match dec_msg.get_content()? { - Some(content) => Ok(content), - None => bail!("Decrypted message is empty"), - } + Ok(content) } /// Symmetric encryption. -pub fn symm_encrypt(passphrase: &str, plain: &[u8]) -> Result { - let mut rng = thread_rng(); +pub async fn symm_encrypt(passphrase: &str, plain: &[u8]) -> Result { let lit_msg = Message::new_literal_bytes("", plain); + let passphrase = passphrase.to_string(); - let s2k = StringToKey::new_default(&mut rng); - let msg = - lit_msg.encrypt_with_password(&mut rng, s2k, Default::default(), || passphrase.into())?; + async_std::task::spawn_blocking(move || { + let mut rng = thread_rng(); + let s2k = StringToKey::new_default(&mut rng); + let msg = + lit_msg.encrypt_with_password(&mut rng, s2k, Default::default(), || passphrase)?; - let encoded_msg = msg.to_armored_string(None)?; + let encoded_msg = msg.to_armored_string(None)?; - Ok(encoded_msg) + Ok(encoded_msg) + }) + .await } /// Symmetric decryption. -pub fn symm_decrypt( +pub async fn symm_decrypt( passphrase: &str, ctext: T, ) -> Result> { let (enc_msg, _) = Message::from_armor_single(ctext)?; - let decryptor = enc_msg.decrypt_with_password(|| passphrase.into())?; - let msgs = decryptor.collect::>>()?; - ensure!(!msgs.is_empty(), "No valid messages found"); + let passphrase = passphrase.to_string(); + async_std::task::spawn_blocking(move || { + let decryptor = enc_msg.decrypt_with_password(|| passphrase)?; - match msgs[0].get_content()? { - Some(content) => Ok(content), - None => bail!("Decrypted message is empty"), - } + let msgs = decryptor.collect::>>()?; + ensure!(!msgs.is_empty(), "No valid messages found"); + + match msgs[0].get_content()? { + Some(content) => Ok(content), + None => bail!("Decrypted message is empty"), + } + }) + .await } #[cfg(test)] @@ -437,17 +453,17 @@ mod tests { /// A cyphertext encrypted to Alice & Bob, signed by Alice. static ref CTEXT_SIGNED: String = { let mut keyring = Keyring::default(); - keyring.add_owned(KEYS.alice_public.clone()); - keyring.add_ref(&KEYS.bob_public); - pk_encrypt(CLEARTEXT, &keyring, Some(&KEYS.alice_secret)).unwrap() + keyring.add(KEYS.alice_public.clone()); + keyring.add(KEYS.bob_public.clone()); + smol::block_on(pk_encrypt(CLEARTEXT, keyring, Some(KEYS.alice_secret.clone()))).unwrap() }; /// A cyphertext encrypted to Alice & Bob, not signed. static ref CTEXT_UNSIGNED: String = { let mut keyring = Keyring::default(); - keyring.add_owned(KEYS.alice_public.clone()); - keyring.add_ref(&KEYS.bob_public); - pk_encrypt(CLEARTEXT, &keyring, None).unwrap() + keyring.add(KEYS.alice_public.clone()); + keyring.add(KEYS.bob_public.clone()); + smol::block_on(pk_encrypt(CLEARTEXT, keyring, None)).unwrap() }; } @@ -463,20 +479,21 @@ mod tests { assert!(CTEXT_UNSIGNED.starts_with("-----BEGIN PGP MESSAGE-----")); } - #[test] - fn test_decrypt_singed() { + #[async_std::test] + async fn test_decrypt_singed() { // Check decrypting as Alice let mut decrypt_keyring = Keyring::default(); - decrypt_keyring.add_ref(&KEYS.alice_secret); + decrypt_keyring.add(KEYS.alice_secret.clone()); let mut sig_check_keyring = Keyring::default(); - sig_check_keyring.add_ref(&KEYS.alice_public); + sig_check_keyring.add(KEYS.alice_public.clone()); let mut valid_signatures: HashSet = Default::default(); let plain = pk_decrypt( - CTEXT_SIGNED.as_bytes(), - &decrypt_keyring, - &sig_check_keyring, + CTEXT_SIGNED.as_bytes().to_vec(), + decrypt_keyring, + sig_check_keyring, Some(&mut valid_signatures), ) + .await .map_err(|err| println!("{:?}", err)) .unwrap(); assert_eq!(plain, CLEARTEXT); @@ -484,89 +501,94 @@ mod tests { // Check decrypting as Bob let mut decrypt_keyring = Keyring::default(); - decrypt_keyring.add_ref(&KEYS.bob_secret); + decrypt_keyring.add(KEYS.bob_secret.clone()); let mut sig_check_keyring = Keyring::default(); - sig_check_keyring.add_ref(&KEYS.alice_public); + sig_check_keyring.add(KEYS.alice_public.clone()); let mut valid_signatures: HashSet = Default::default(); let plain = pk_decrypt( - CTEXT_SIGNED.as_bytes(), - &decrypt_keyring, - &sig_check_keyring, + CTEXT_SIGNED.as_bytes().to_vec(), + decrypt_keyring, + sig_check_keyring, Some(&mut valid_signatures), ) + .await .map_err(|err| println!("{:?}", err)) .unwrap(); assert_eq!(plain, CLEARTEXT); assert_eq!(valid_signatures.len(), 1); } - #[test] - fn test_decrypt_no_sig_check() { + #[async_std::test] + async fn test_decrypt_no_sig_check() { let mut keyring = Keyring::default(); - keyring.add_ref(&KEYS.alice_secret); + keyring.add(KEYS.alice_secret.clone()); let empty_keyring = Keyring::default(); let mut valid_signatures: HashSet = Default::default(); let plain = pk_decrypt( - CTEXT_SIGNED.as_bytes(), - &keyring, - &empty_keyring, + CTEXT_SIGNED.as_bytes().to_vec(), + keyring, + empty_keyring, Some(&mut valid_signatures), ) + .await .unwrap(); assert_eq!(plain, CLEARTEXT); assert_eq!(valid_signatures.len(), 0); } - #[test] - fn test_decrypt_signed_no_key() { + #[async_std::test] + async fn test_decrypt_signed_no_key() { // The validation does not have the public key of the signer. let mut decrypt_keyring = Keyring::default(); - decrypt_keyring.add_ref(&KEYS.bob_secret); + decrypt_keyring.add(KEYS.bob_secret.clone()); let mut sig_check_keyring = Keyring::default(); - sig_check_keyring.add_ref(&KEYS.bob_public); + sig_check_keyring.add(KEYS.bob_public.clone()); let mut valid_signatures: HashSet = Default::default(); let plain = pk_decrypt( - CTEXT_SIGNED.as_bytes(), - &decrypt_keyring, - &sig_check_keyring, + CTEXT_SIGNED.as_bytes().to_vec(), + decrypt_keyring, + sig_check_keyring, Some(&mut valid_signatures), ) + .await .unwrap(); assert_eq!(plain, CLEARTEXT); assert_eq!(valid_signatures.len(), 0); } - #[test] - fn test_decrypt_unsigned() { + #[async_std::test] + async fn test_decrypt_unsigned() { let mut decrypt_keyring = Keyring::default(); - decrypt_keyring.add_ref(&KEYS.bob_secret); + decrypt_keyring.add(KEYS.bob_secret.clone()); let sig_check_keyring = Keyring::default(); - decrypt_keyring.add_ref(&KEYS.alice_public); + decrypt_keyring.add(KEYS.alice_public.clone()); let mut valid_signatures: HashSet = Default::default(); let plain = pk_decrypt( - CTEXT_UNSIGNED.as_bytes(), - &decrypt_keyring, - &sig_check_keyring, + CTEXT_UNSIGNED.as_bytes().to_vec(), + decrypt_keyring, + sig_check_keyring, Some(&mut valid_signatures), ) + .await .unwrap(); assert_eq!(plain, CLEARTEXT); assert_eq!(valid_signatures.len(), 0); } - #[test] - fn test_decrypt_signed_no_sigret() { + #[async_std::test] + async fn test_decrypt_signed_no_sigret() { // Check decrypting signed cyphertext without providing the HashSet for signatures. let mut decrypt_keyring = Keyring::default(); - decrypt_keyring.add_ref(&KEYS.bob_secret); + decrypt_keyring.add(KEYS.bob_secret.clone()); let mut sig_check_keyring = Keyring::default(); - sig_check_keyring.add_ref(&KEYS.alice_public); + sig_check_keyring.add(KEYS.alice_public.clone()); let plain = pk_decrypt( - CTEXT_SIGNED.as_bytes(), - &decrypt_keyring, - &sig_check_keyring, + CTEXT_SIGNED.as_bytes().to_vec(), + decrypt_keyring, + sig_check_keyring, None, ) + .await .unwrap(); assert_eq!(plain, CLEARTEXT); } From 45aba61ac8fa5a9d190d6f25f43cc5e4e7b50e7b Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Mon, 25 May 2020 00:56:58 +0200 Subject: [PATCH 107/118] update smol fork --- Cargo.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3d00952a6..cea68cf81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2134,9 +2134,9 @@ dependencies = [ [[package]] name = "proc-macro-hack" -version = "0.5.15" +version = "0.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63" +checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4" [[package]] name = "proc-macro-nested" @@ -2792,7 +2792,7 @@ checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" [[package]] name = "smol" version = "0.1.10" -source = "git+https://github.com/dignifiedquire/smol-1?branch=isolate-nix#8b35e961d82e4543feadec86828500092b5e1938" +source = "git+https://github.com/dignifiedquire/smol-1?branch=isolate-nix#216aaf4e7765eb7c764db175936a734ff2b927f6" dependencies = [ "async-task", "crossbeam", From 230d40daa0b3e2d74f2dc63ff937ef86a3b3f3c7 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Mon, 25 May 2020 01:05:17 +0200 Subject: [PATCH 108/118] fixup dependency --- Cargo.lock | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cea68cf81..d9ef44466 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -582,30 +582,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crossbeam" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69323bff1fb41c635347b8ead484a5ca6c3f11914d784170b158d8449ab07f8e" -dependencies = [ - "cfg-if", - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-epoch", - "crossbeam-queue", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-channel" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061" -dependencies = [ - "crossbeam-utils", - "maybe-uninit", -] - [[package]] name = "crossbeam-deque" version = "0.7.3" @@ -2792,10 +2768,12 @@ checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" [[package]] name = "smol" version = "0.1.10" -source = "git+https://github.com/dignifiedquire/smol-1?branch=isolate-nix#216aaf4e7765eb7c764db175936a734ff2b927f6" +source = "git+https://github.com/dignifiedquire/smol-1?branch=isolate-nix#41311e709401773c167819d98a56e0c1762d2ba5" dependencies = [ "async-task", - "crossbeam", + "crossbeam-deque", + "crossbeam-queue", + "crossbeam-utils", "futures-io", "futures-util", "libc", From 13811c06ee0972a627cf04cd86796ada191c849b Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Mon, 25 May 2020 12:08:16 +0200 Subject: [PATCH 109/118] mark generate_key as blocking --- src/key.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/key.rs b/src/key.rs index c00f77486..035507b5e 100644 --- a/src/key.rs +++ b/src/key.rs @@ -174,7 +174,9 @@ async fn generate_keypair(context: &Context) -> Result { let keytype = KeyGenType::from_i32(context.get_config_int(Config::KeyGenType).await) .unwrap_or_default(); info!(context, "Generating keypair with type {}", keytype); - let keypair = crate::pgp::create_keypair(addr, keytype)?; + let keypair = + async_std::task::spawn_blocking(move || crate::pgp::create_keypair(addr, keytype)) + .await?; store_self_keypair(context, &keypair, KeyPairUse::Default).await?; info!( context, From 2156c6cd7a4b3ade7e45df9da492ed953bcfb2bf Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Mon, 25 May 2020 12:28:04 +0200 Subject: [PATCH 110/118] basic documentation of ffi --- deltachat-ffi/deltachat.h | 312 ++++++++++++++++++++++---------------- 1 file changed, 184 insertions(+), 128 deletions(-) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index d084dfc68..25d63057e 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -32,63 +32,41 @@ typedef struct _dc_event_emitter dc_event_emitter_t; * * Let's start. * - * First of all, you have to **define an event-handler-function** - * that is called by the library on specific events - * (eg. when the configuration is done or when fresh messages arrive). - * With this function you can create a Delta Chat context then: + * First of all, you have to **create a context object** + * bound to a database. + * The database is a normal sqlite-file and is created as needed: * * ~~~ - * #include + * dc_context_t* context = dc_context_new(NULL, "example.db", NULL); + * ~~~ * - * uintptr_t event_handler_func(dc_context_t* context, int event, - * uintptr_t data1, uintptr_t data2) + * After that, make sure, you can **receive events from the context**. + * For that purpose, create an event emitter you can ask for events. + * If there are no event, the emitter will wait until there is one, + * so, in many situations you will do this in a thread: + * + * ~~~ + * dc_event_emitter_t* emitter = dc_get_event_emitter(context); + * + * void* event_handler(void* emitter) * { - * return 0; - * } - * - * dc_context_t* context = dc_context_new(event_handler_func, NULL, NULL); - * ~~~ - * - * After that, you should make sure, - * sending and receiving jobs are processed as needed. - * For this purpose, you have to **create two threads:** - * - * ~~~ - * #include - * - * void* imap_thread_func(void* context) - * { - * while (true) { - * dc_perform_imap_jobs(context); - * dc_perform_imap_fetch(context); - * dc_perform_imap_idle(context); + * dc_event_t* event; + * while ((event = dc_get_next_event(emitter)) != NULL) { + * // use the event as needed, eg. dc_event_get_id() returns the type. + * // once you're done, unref the event to avoid memory leakage: + * dc_event_unref(event); * } + * dc_event_emitter_unref(emitter); * } * - * void* smtp_thread_func(void* context) - * { - * while (true) { - * dc_perform_smtp_jobs(context); - * dc_perform_smtp_idle(context); - * } - * } - * - * static pthread_t imap_thread, smtp_thread; - * pthread_create(&imap_thread, NULL, imap_thread_func, context); - * pthread_create(&smtp_thread, NULL, smtp_thread_func, context); + * static pthread_t event_thread; + * pthread_create(&event_thread, NULL, event_handler, emitter); * ~~~ * * The example above uses "pthreads", * however, you can also use anything else for thread handling. * All deltachat-core-functions, unless stated otherwise, are thread-safe. * - * After that you can **define and open a database.** - * The database is a normal sqlite-file and is created as needed: - * - * ~~~ - * dc_open(context, "example.db", NULL); - * ~~~ - * * Now you can **configure the context:** * * ~~~ @@ -98,15 +76,22 @@ typedef struct _dc_event_emitter dc_event_emitter_t; * dc_configure(context); * ~~~ * - * dc_configure() returns immediately, the configuration itself may take a while - * and is done by a job in the imap-thread you've defined above. + * dc_configure() returns immediately, + * the configuration itself runs in background and may take a while. * Once done, the #DC_EVENT_CONFIGURE_PROGRESS reports success - * to the event_handler_func() that is also defined above. + * to the event_handler() you've defined above. * * The configuration result is saved in the database, * on subsequent starts it is not needed to call dc_configure() * (you can check this using dc_is_configured()). * + * On a successfully configured context, + * you can finally **connect to the servers:** + * + * ~~~ + * dc_start_io(context); + * ~~~ + * * Now you can **send the first message:** * * ~~~ @@ -166,19 +151,6 @@ typedef struct _dc_event_emitter dc_event_emitter_t; * - The issue-tracker for the core library is here: * * - * The following points are important mainly - * for the authors of the library itself: - * - * - For indentation, use tabs. - * Alignments that are not placed at the beginning of a line - * should be done with spaces. - * - * - For padding between functions, - * classes etc. use 2 empty lines - * - * - Source files are encoded as UTF-8 with Unix line endings - * (a simple `LF`, `0x0A` or `\n`) - * * If you need further assistance, * please do not hesitate to contact us * through the channels shown at https://delta.chat/en/contribute @@ -191,24 +163,6 @@ typedef struct _dc_event_emitter dc_event_emitter_t; */ -/** - * TODO: document - */ -dc_event_t* dc_get_next_event(dc_event_emitter_t* emitter); - -dc_event_emitter_t* dc_get_event_emitter(dc_context_t* context); -void dc_event_emitter_unref(dc_event_emitter_t* emitter); - -int dc_event_get_id (dc_event_t* event); -int dc_event_get_data1_int(dc_event_t* event); -int dc_event_get_data2_int(dc_event_t* event); -char* dc_event_get_data3_str(dc_event_t* event); - -/** - * TODO: document - */ -void dc_event_unref (dc_event_t* event); - /** * @class dc_context_t * @@ -226,21 +180,6 @@ void dc_event_unref (dc_event_t* event); * opened, connected and mails are fetched. * * @memberof dc_context_t - * @param cb a callback function that is called for events (update, - * state changes etc.) and to get some information from the client (eg. translation - * for a given string). - * See @ref DC_EVENT for a list of possible events that may be passed to the callback. - * - The callback MAY be called from _any_ thread, not only the main/GUI thread! - * - The callback MUST NOT call any dc_* and related functions unless stated - * otherwise! - * - The callback SHOULD return _fast_, for GUI updates etc. you should - * post yourself an asynchronous message to your GUI thread, if needed. - * - events do not expect a return value, just always return 0. - * @param dbfile The file to use to store the database, something like `~/file` won't - * work on all systems, if in doubt, use absolute paths. - * @param blobdir A directory to store the blobs in; a trailing slash is not needed. - * If you pass NULL or the empty string, deltachat-core creates a directory - * beside _dbfile_ with the same name and the suffix `-blobs`. * @param os_name is only for decorative use * and is shown eg. in the `X-Mailer:` header * in the form "Delta Chat Core /". @@ -248,6 +187,11 @@ void dc_event_unref (dc_event_t* event); * the used environment and/or the version here. * It is okay to give NULL, in this case `X-Mailer:` header * is set to "Delta Chat Core ". + * @param dbfile The file to use to store the database, + * something like `~/file` won't work, use absolute paths. + * @param blobdir A directory to store the blobs in; a trailing slash is not needed. + * If you pass NULL or the empty string, deltachat-core creates a directory + * beside _dbfile_ with the same name and the suffix `-blobs`. * @return A context object with some public members. * The object must be passed to the other context functions * and must be freed using dc_context_unref() after usage. @@ -269,6 +213,25 @@ dc_context_t* dc_context_new (const char* os_name, const char* d */ void dc_context_unref (dc_context_t* context); + +/** + * Create the event emitter that is used to receive events. + * The library will emit various @ref DC_EVENT events as "new message", "message read" etc. + * To get these events, you have to create an event emitter using this function + * and call dc_get_next_event() on the emitter. + * + * @memberof dc_context_t + * @param context The context object as created by dc_context_new(). + * @return Returns the event emitter, NULL on errors. + * Must be freed using dc_event_emitter_unref() after usage. + * + * Note: Use only one event emitter per context. + * Having more than one event emitter running at the same time on the same context + * will result in events randomly delivered to the one or to the other. + */ +dc_event_emitter_t* dc_get_event_emitter(dc_context_t* context); + + /** * Get the blob directory. * @@ -3857,11 +3820,122 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot); #define DC_EMPTY_INBOX 0x02 // Deprecated, flag for dc_empty_server(): Clear all INBOX messages +/** + * @class dc_event_emitter_t + * + * Opaque object that is used to get events. + * You can get an event emitter from a context using dc_get_event_emitter(). + */ + +/** + * Get the next event from an event emitter object. + * + * @memberof dc_event_emitter_t + * @param emitter Event emitter object as returned from dc_get_event_emitter(). + * @return An event as an dc_event_t object. + * You can query the event for information using dc_event_get_id(), dc_event_get_data1_int() and so on; + * if you are done with the event, you have to free the event using dc_event_unref(). + * If NULL is returned, the context belonging to the event emitter is unref'd and the no more events will come; + * in this case, free the event emitter using dc_event_emitter_unref(). + */ +dc_event_t* dc_get_next_event(dc_event_emitter_t* emitter); + + +/** + * Free an event emitter object. + * + * @memberof dc_event_emitter_t + * @param emitter Event emitter object as returned from dc_get_event_emitter(). + * If NULL is given, nothing is done and an error is logged. + * @return None. + */ +void dc_event_emitter_unref(dc_event_emitter_t* emitter); + + +/** + * @class dc_event_t + * + * Opaque object describing a single event. + * To get events, call dc_get_next_event() on an event emitter created by dc_get_event_emitter(). + */ + +/** + * Get the event-id from an event object. + * The event-id is one of the @ref DC_EVENT constants. + * There may be additional data belonging to an event, + * to get them, use dc_event_get_data1_int(), dc_event_get_data2_int() and dc_event_get_data3_str(). + * + * @memberof dc_event_t + * @param event Event object as returned from dc_get_next_event(). + * @return once of the @ref DC_EVENT constants. + * 0 on errors. + */ +int dc_event_get_id(dc_event_t* event); + + +/** + * Get a data associated with an event object. + * The meaning of the data depends on the event-id + * returned as @ref DC_EVENT constants by dc_event_get_id(). + * See also dc_event_get_data2_int() and dc_event_get_data3_str(). + * + * @memberof dc_event_t + * @param event Event object as returned from dc_get_next_event(). + * @return "data1" as a signed integer, at least 32bit, + * the meaning depends on the event type associated with this event. + */ +int dc_event_get_data1_int(dc_event_t* event); + + +/** + * Get a data associated with an event object. + * The meaning of the data depends on the event-id + * returned as @ref DC_EVENT constants by dc_event_get_id(). + * See also dc_event_get_data2_int() and dc_event_get_data3_str(). + * + * @memberof dc_event_t + * @param event Event object as returned from dc_get_next_event(). + * @return "data2" as a signed integer, at least 32bit, + * the meaning depends on the event type associated with this event. + */ +int dc_event_get_data2_int(dc_event_t* event); + + +/** + * Get a data associated with an event object. + * The meaning of the data depends on the event-id + * returned as @ref DC_EVENT constants by dc_event_get_id(). + * See also dc_event_get_data1_int() and dc_event_get_data2_int(). + * + * @memberof dc_event_t + * @param event Event object as returned from dc_get_next_event(). + * @return "data3" as a string, + * the meaning depends on the event type associated with this event. + * Once you're done with the string, you have to unref it using dc_unref_str(). + */ +char* dc_event_get_data3_str(dc_event_t* event); + + +/** + * Free memory used by an event object. + * If you forget to do this for an event, this will result in memory leakage. + * + * @memberof dc_event_t + * @param event Event object as returned from dc_get_next_event(). + * @return None. + */ +void dc_event_unref(dc_event_t* event); + + /** * @defgroup DC_EVENT DC_EVENT * - * These constants are used as events - * reported to the callback given to dc_context_new(). + * These constants are used as event-id + * in events returned by dc_get_next_event(). + * + * Events typically come with some additional data, + * use dc_event_get_data1_int(), dc_event_get_data2_int() and dc_event_get_data3_str() to read this data. + * The meaning of the data depends on the event. * * @addtogroup DC_EVENT * @{ @@ -3869,13 +3943,11 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot); /** * The library-user may write an informational string to the log. - * Passed to the callback given to dc_context_new(). * * This event should not be reported to the end-user using a popup or something like that. * * @param data1 0 - * @param data2 (const char*) Info string in english language. - * Must not be unref'd or modified and is valid only until the callback returns. + * @param data2 (char*) Info string in english language. */ #define DC_EVENT_INFO 100 @@ -3884,8 +3956,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot); * Emitted when SMTP connection is established and login was successful. * * @param data1 0 - * @param data2 (const char*) Info string in english language. - * Must not be unref'd or modified and is valid only until the callback returns. + * @param data2 (char*) Info string in english language. */ #define DC_EVENT_SMTP_CONNECTED 101 @@ -3894,8 +3965,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot); * Emitted when IMAP connection is established and login was successful. * * @param data1 0 - * @param data2 (const char*) Info string in english language. - * Must not be unref'd or modified and is valid only until the callback returns. + * @param data2 (char*) Info string in english language. */ #define DC_EVENT_IMAP_CONNECTED 102 @@ -3903,8 +3973,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot); * Emitted when a message was successfully sent to the SMTP server. * * @param data1 0 - * @param data2 (const char*) Info string in english language. - * Must not be unref'd or modified and is valid only until the callback returns. + * @param data2 (char*) Info string in english language. */ #define DC_EVENT_SMTP_MESSAGE_SENT 103 @@ -3912,8 +3981,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot); * Emitted when a message was successfully marked as deleted on the IMAP server. * * @param data1 0 - * @param data2 (const char*) Info string in english language. - * Must not be unref'd or modified and is valid only until the callback returns. + * @param data2 (char*) Info string in english language. */ #define DC_EVENT_IMAP_MESSAGE_DELETED 104 @@ -3921,8 +3989,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot); * Emitted when a message was successfully moved on IMAP. * * @param data1 0 - * @param data2 (const char*) Info string in english language. - * Must not be unref'd or modified and is valid only until the callback returns. + * @param data2 (char*) Info string in english language. */ #define DC_EVENT_IMAP_MESSAGE_MOVED 105 @@ -3930,8 +3997,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot); * Emitted when an IMAP folder was emptied. * * @param data1 0 - * @param data2 (const char*) folder name. - * Must not be unref'd or modified and is valid only until the callback returns. + * @param data2 (char*) Folder name. */ #define DC_EVENT_IMAP_FOLDER_EMPTIED 106 @@ -3939,8 +4005,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot); * Emitted when a new blob file was successfully written * * @param data1 0 - * @param data2 (const char*) path name - * Must not be unref'd or modified and is valid only until the callback returns. + * @param data2 (char*) Path name */ #define DC_EVENT_NEW_BLOB_FILE 150 @@ -3948,27 +4013,23 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot); * Emitted when a blob file was successfully deleted * * @param data1 0 - * @param data2 (const char*) path name - * Must not be unref'd or modified and is valid only until the callback returns. + * @param data2 (char*) Path name */ #define DC_EVENT_DELETED_BLOB_FILE 151 /** * The library-user should write a warning string to the log. - * Passed to the callback given to dc_context_new(). * * This event should not be reported to the end-user using a popup or something like that. * * @param data1 0 - * @param data2 (const char*) Warning string in english language. - * Must not be unref'd or modified and is valid only until the callback returns. + * @param data2 (char*) Warning string in english language. */ #define DC_EVENT_WARNING 300 /** * The library-user should report an error to the end-user. - * Passed to the callback given to dc_context_new(). * * As most things are asynchronous, things may go wrong at any time and the user * should not be disturbed by a dialog or so. Instead, use a bubble or so. @@ -3980,10 +4041,9 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot); * in a messasge box then. * * @param data1 0 - * @param data2 (const char*) Error string, always set, never NULL. + * @param data2 (char*) Error string, always set, never NULL. * Some error strings are taken from dc_set_stock_translation(), * however, most error strings will be in english language. - * Must not be unref'd or modified and is valid only until the callback returns. */ #define DC_EVENT_ERROR 400 @@ -4005,8 +4065,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot); * * @param data1 (int) 1=first/new network error, should be reported the user; * 0=subsequent network error, should be logged only - * @param data2 (const char*) Error string, always set, never NULL. - * Must not be unref'd or modified and is valid only until the callback returns. + * @param data2 (char*) Error string, always set, never NULL. */ #define DC_EVENT_ERROR_NETWORK 401 @@ -4019,9 +4078,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot); * dc_send_text_msg() or another sending function. * * @param data1 0 - * @param data2 (const char*) Info string in english language. - * Must not be unref'd or modified - * and is valid only until the callback returns. + * @param data2 (char*) Info string in english language. */ #define DC_EVENT_ERROR_SELF_NOT_IN_GROUP 410 @@ -4139,9 +4196,8 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot); * A typical purpose for a handler of this event may be to make the file public to some system * services. * - * @param data1 (const char*) Path and file name. - * Must not be unref'd or modified and is valid only until the callback returns. - * @param data2 0 + * @param data1 0 + * @param data2 (char*) Path and file name. */ #define DC_EVENT_IMEX_FILE_WRITTEN 2052 From ec601a3381943600852d5a4897e3121dd85a08f3 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Mon, 25 May 2020 12:58:21 +0200 Subject: [PATCH 111/118] revert renaming of data2_str to data3_str, while this looks clearer at a first glance, it would mean to introduce much noise in the existing bindings and understandings --- deltachat-ffi/deltachat.h | 12 ++++++------ deltachat-ffi/src/lib.rs | 4 ++-- python/src/deltachat/events.py | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 25d63057e..b6e2f74cc 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -3863,7 +3863,7 @@ void dc_event_emitter_unref(dc_event_emitter_t* emitter); * Get the event-id from an event object. * The event-id is one of the @ref DC_EVENT constants. * There may be additional data belonging to an event, - * to get them, use dc_event_get_data1_int(), dc_event_get_data2_int() and dc_event_get_data3_str(). + * to get them, use dc_event_get_data1_int(), dc_event_get_data2_int() and dc_event_get_data2_str(). * * @memberof dc_event_t * @param event Event object as returned from dc_get_next_event(). @@ -3877,7 +3877,7 @@ int dc_event_get_id(dc_event_t* event); * Get a data associated with an event object. * The meaning of the data depends on the event-id * returned as @ref DC_EVENT constants by dc_event_get_id(). - * See also dc_event_get_data2_int() and dc_event_get_data3_str(). + * See also dc_event_get_data2_int() and dc_event_get_data2_str(). * * @memberof dc_event_t * @param event Event object as returned from dc_get_next_event(). @@ -3891,7 +3891,7 @@ int dc_event_get_data1_int(dc_event_t* event); * Get a data associated with an event object. * The meaning of the data depends on the event-id * returned as @ref DC_EVENT constants by dc_event_get_id(). - * See also dc_event_get_data2_int() and dc_event_get_data3_str(). + * See also dc_event_get_data2_int() and dc_event_get_data2_str(). * * @memberof dc_event_t * @param event Event object as returned from dc_get_next_event(). @@ -3909,11 +3909,11 @@ int dc_event_get_data2_int(dc_event_t* event); * * @memberof dc_event_t * @param event Event object as returned from dc_get_next_event(). - * @return "data3" as a string, + * @return "data2" as a string, * the meaning depends on the event type associated with this event. * Once you're done with the string, you have to unref it using dc_unref_str(). */ -char* dc_event_get_data3_str(dc_event_t* event); +char* dc_event_get_data2_str(dc_event_t* event); /** @@ -3934,7 +3934,7 @@ void dc_event_unref(dc_event_t* event); * in events returned by dc_get_next_event(). * * Events typically come with some additional data, - * use dc_event_get_data1_int(), dc_event_get_data2_int() and dc_event_get_data3_str() to read this data. + * use dc_event_get_data1_int(), dc_event_get_data2_int() and dc_event_get_data2_str() to read this data. * The meaning of the data depends on the event. * * @addtogroup DC_EVENT diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 5e4a6b51b..f3fe11c37 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -403,9 +403,9 @@ pub unsafe extern "C" fn dc_event_get_data2_int(event: *mut dc_event_t) -> libc: } #[no_mangle] -pub unsafe extern "C" fn dc_event_get_data3_str(event: *mut dc_event_t) -> *mut libc::c_char { +pub unsafe extern "C" fn dc_event_get_data2_str(event: *mut dc_event_t) -> *mut libc::c_char { if event.is_null() { - eprintln!("ignoring careless call to dc_event_get_data3_str()"); + eprintln!("ignoring careless call to dc_event_get_data2_str()"); return ptr::null_mut(); } diff --git a/python/src/deltachat/events.py b/python/src/deltachat/events.py index 43999fe0e..967002f65 100644 --- a/python/src/deltachat/events.py +++ b/python/src/deltachat/events.py @@ -172,7 +172,7 @@ class EventThread(threading.Thread): # function which provides us signature info of an event call evt_name = deltachat.get_dc_event_name(evt) if lib.dc_event_has_string_data(evt): - data2 = from_dc_charpointer(lib.dc_event_get_data3_str(event)) + data2 = from_dc_charpointer(lib.dc_event_get_data2_str(event)) else: data2 = lib.dc_event_get_data2_int(event) From 157dd44df045558d4822eb0010fce3e3f2ff4049 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Mon, 25 May 2020 17:03:08 +0200 Subject: [PATCH 112/118] refactor: improve structure of fetch_messages also fixes updating last_seen_uid to the correct value --- src/imap/mod.rs | 341 +++++++++++++++++++++++++----------------------- 1 file changed, 181 insertions(+), 160 deletions(-) diff --git a/src/imap/mod.rs b/src/imap/mod.rs index f235a4f21..6032fb873 100644 --- a/src/imap/mod.rs +++ b/src/imap/mod.rs @@ -3,7 +3,7 @@ //! uses [async-email/async-imap](https://github.com/async-email/async-imap) //! to implement connect, fetch, delete functionality with standard IMAP servers. -use num_traits::FromPrimitive; +use std::collections::BTreeMap; use async_imap::{ error::Result as ImapResult, @@ -11,6 +11,7 @@ use async_imap::{ }; use async_std::prelude::*; use async_std::sync::Receiver; +use num_traits::FromPrimitive; use crate::config::*; use crate::constants::*; @@ -577,154 +578,120 @@ impl Imap { .select_with_uidvalidity(context, folder.as_ref()) .await?; - let mut read_cnt: usize = 0; - - if self.session.is_none() { - return Err(Error::NoConnection); - } - let session = self.session.as_mut().unwrap(); - - // fetch messages with larger UID than the last one seen - // `(UID FETCH lastseenuid+1:*)`, see RFC 4549 - let set = format!("{}:*", last_seen_uid + 1); - let mut list = match session.uid_fetch(set, PREFETCH_FLAGS).await { - Ok(list) => list, - Err(err) => { - return Err(Error::FetchFailed(err)); - } - }; - - let mut msgs = Vec::new(); - while let Some(fetch) = list.next().await { - let fetch = fetch.map_err(|err| Error::Other(err.to_string()))?; - msgs.push(fetch); - } - drop(list); - - msgs.sort_unstable_by_key(|msg| msg.uid.unwrap_or_default()); - let msgs: Vec<_> = msgs - .into_iter() - .filter(|msg| { - let cur_uid = msg.uid.unwrap_or_default(); - if cur_uid <= last_seen_uid { - // If the mailbox is not empty, results always include - // at least one UID, even if last_seen_uid+1 is past - // the last UID in the mailbox. It happens because - // uid+1:* is interpreted the same way as *:uid+1. - // See https://tools.ietf.org/html/rfc3501#page-61 for - // standard reference. Therefore, sometimes we receive - // already seen messages and have to filter them out. - info!( - context, - "fetch_new_messages: ignoring uid {}, last seen was {}", - cur_uid, - last_seen_uid - ); - false - } else { - true - } - }) - .collect(); - - read_cnt += msgs.len(); + let msgs = self.fetch_after(context, last_seen_uid).await?; + let read_cnt = msgs.len(); + let folder: &str = folder.as_ref(); let mut read_errors = 0; - let mut uids = Vec::with_capacity(msgs.len()); let mut new_last_seen_uid = None; - for fetch in msgs.into_iter() { - let folder: &str = folder.as_ref(); - - let cur_uid = fetch.uid.unwrap_or_default(); - let headers = match get_fetch_headers(&fetch) { - Ok(h) => h, + for (current_uid, msg) in msgs.into_iter() { + let (headers, msg_id) = match get_fetch_headers(&msg) { + Ok(headers) => { + let msg_id = prefetch_get_message_id(&headers).unwrap_or_default(); + (headers, msg_id) + } Err(err) => { - warn!(context, "get_fetch_headers error: {}", err); + warn!(context, "{}", err); read_errors += 1; continue; } }; - let message_id = prefetch_get_message_id(&headers).unwrap_or_default(); - let skip = match precheck_imf(context, &message_id, folder, cur_uid).await { - Ok(skip) => skip, - Err(err) => { - warn!(context, "precheck_imf error: {}", err); - true - } - }; - - if skip { - // we know the message-id already or don't want the message otherwise. - info!( - context, - "Skipping message {} from \"{}\" by precheck.", message_id, folder, - ); - if read_errors == 0 { - new_last_seen_uid = Some(cur_uid); - } - } else { - // we do not know the message-id - // or the message-id is missing (in this case, we create one in the further process) - // or some other error happened - let show = match prefetch_should_download(context, &headers, show_emails).await { - Ok(show) => show, - Err(err) => { - warn!(context, "prefetch_should_download error: {}", err); - true - } - }; - - if show { - uids.push(cur_uid); - } else { - info!( - context, - "Ignoring new message {} from \"{}\".", message_id, folder, - ); - } - if read_errors == 0 { - new_last_seen_uid = Some(cur_uid); - } + if message_needs_processing( + context, + current_uid, + &headers, + &msg_id, + folder, + show_emails, + ) + .await + { + // Trigger download and processing for this message. + uids.push(current_uid); + } else if read_errors == 0 { + // No errors so far, but this was skipped, so mark as last_seen_uid + new_last_seen_uid = Some(current_uid); } } // check passed, go fetch the emails let (new_last_seen_uid_processed, error_cnt) = self.fetch_many_msgs(context, &folder, &uids).await; + read_errors += error_cnt; + // determine which last_seen_uid to use to update to let new_last_seen_uid_processed = new_last_seen_uid_processed.unwrap_or_default(); let new_last_seen_uid = new_last_seen_uid.unwrap_or_default(); let last_one = new_last_seen_uid.max(new_last_seen_uid_processed); + if last_one > last_seen_uid { - self.set_config_last_seen_uid(context, &folder, uid_validity, new_last_seen_uid) + self.set_config_last_seen_uid(context, &folder, uid_validity, last_one) .await; } - read_errors += error_cnt; - - if read_errors > 0 { + if read_errors == 0 { + info!(context, "{} mails read from \"{}\".", read_cnt, folder,); + } else { warn!( context, - "{} mails read from \"{}\" with {} errors.", - read_cnt, - folder.as_ref(), - read_errors - ); - } else { - info!( - context, - "{} mails read from \"{}\".", - read_cnt, - folder.as_ref() + "{} mails read from \"{}\" with {} errors.", read_cnt, folder, read_errors ); } Ok(read_cnt > 0) } + /// Fetch all uids larger than the passed in. Returns a sorted list of fetch results. + async fn fetch_after( + &mut self, + context: &Context, + uid: u32, + ) -> Result> { + if self.session.is_none() { + return Err(Error::NoConnection); + } + + let session = self.session.as_mut().unwrap(); + + // fetch messages with larger UID than the last one seen + // `(UID FETCH lastseenuid+1:*)`, see RFC 4549 + let set = format!("{}:*", uid + 1); + let mut list = session + .uid_fetch(set, PREFETCH_FLAGS) + .await + .map_err(Error::FetchFailed)?; + + let mut msgs = BTreeMap::new(); + while let Some(fetch) = list.next().await { + let msg = fetch.map_err(|err| Error::Other(err.to_string()))?; + if let Some(msg_uid) = msg.uid { + msgs.insert(msg_uid, msg); + } + } + drop(list); + + // If the mailbox is not empty, results always include + // at least one UID, even if last_seen_uid+1 is past + // the last UID in the mailbox. It happens because + // uid+1:* is interpreted the same way as *:uid+1. + // See https://tools.ietf.org/html/rfc3501#page-61 for + // standard reference. Therefore, sometimes we receive + // already seen messages and have to filter them out. + let new_msgs = msgs.split_off(&(uid + 1)); + + for current_uid in msgs.keys() { + info!( + context, + "fetch_new_messages: ignoring uid {}, last seen was {}", current_uid, uid + ); + } + + Ok(new_msgs) + } + async fn set_config_last_seen_uid>( &self, context: &Context, @@ -756,6 +723,20 @@ impl Imap { return (None, 0); } + if !self.is_connected() { + warn!(context, "Not connected"); + return (None, server_uids.len()); + } + + if self.session.is_none() { + // we could not get a valid imap session, this should be retried + self.trigger_reconnect(); + warn!(context, "Could not get IMAP session"); + return (None, server_uids.len()); + } + + let session = self.session.as_mut().unwrap(); + let set = if server_uids.len() == 1 { server_uids[0].to_string() } else { @@ -765,74 +746,67 @@ impl Imap { format!("{}:{}", first_uid, last_uid) }; - if !self.is_connected() { - warn!(context, "Not connected"); - return (None, server_uids.len()); - } - - let mut msgs = if let Some(ref mut session) = &mut self.session { - match session.uid_fetch(&set, BODY_FLAGS).await { - Ok(msgs) => msgs, - Err(err) => { - // 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, - "Error on fetching messages #{} from folder \"{}\"; error={}.", - &set, - folder.as_ref(), - err - ); - return (None, server_uids.len()); - } + let mut msgs = match session.uid_fetch(&set, BODY_FLAGS).await { + Ok(msgs) => msgs, + Err(err) => { + // 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, + "Error on fetching messages #{} from folder \"{}\"; error={}.", + &set, + folder.as_ref(), + err + ); + return (None, server_uids.len()); } - } else { - // we could not get a valid imap session, this should be retried - self.trigger_reconnect(); - warn!(context, "Could not get IMAP session"); - return (None, server_uids.len()); }; + let folder = folder.as_ref().to_string(); + let mut read_errors = 0; let mut last_uid = None; let mut count = 0; - let mut jobs = Vec::with_capacity(server_uids.len()); - + let mut tasks = Vec::with_capacity(server_uids.len()); while let Some(Ok(msg)) = msgs.next().await { let server_uid = msg.uid.unwrap_or_default(); + if !server_uids.contains(&server_uid) { // skip if there are some in between we are not interested in continue; } count += 1; - // XXX put flags into a set and pass them to dc_receive_imf let is_deleted = msg.flags().any(|flag| flag == Flag::Deleted); - let is_seen = msg.flags().any(|flag| flag == Flag::Seen); + if is_deleted || msg.body().is_none() { + // No need to process these. + continue; + } - if !is_deleted && msg.body().is_some() { - let folder = folder.as_ref().to_string(); - let context = context.clone(); - let task = async_std::task::spawn(async move { - let body = msg.body().unwrap_or_default(); + // XXX put flags into a set and pass them to dc_receive_imf + let context = context.clone(); + let folder = folder.clone(); - if let Err(err) = - dc_receive_imf(&context, &body, &folder, server_uid, is_seen).await - { + let task = async_std::task::spawn(async move { + // safe, as we checked above that there is a body. + let body = msg.body().unwrap(); + let is_seen = msg.flags().any(|flag| flag == Flag::Seen); + + match dc_receive_imf(&context, &body, &folder, server_uid, is_seen).await { + Ok(_) => Some(server_uid), + Err(err) => { warn!(context, "dc_receive_imf error: {}", err); read_errors += 1; None - } else { - Some(server_uid) } - }); - jobs.push(task); - } + } + }); + tasks.push(task); } - for task in futures::future::join_all(jobs).await { + for task in futures::future::join_all(tasks).await { match task { Some(uid) => { last_uid = Some(uid); @@ -1493,3 +1467,50 @@ async fn prefetch_should_download( let show = show && !blocked_contact; Ok(show) } + +async fn message_needs_processing( + context: &Context, + current_uid: u32, + headers: &[mailparse::MailHeader<'_>], + msg_id: &str, + folder: &str, + show_emails: ShowEmails, +) -> bool { + let skip = match precheck_imf(context, &msg_id, folder, current_uid).await { + Ok(skip) => skip, + Err(err) => { + warn!(context, "precheck_imf error: {}", err); + true + } + }; + + if skip { + // we know the message-id already or don't want the message otherwise. + info!( + context, + "Skipping message {} from \"{}\" by precheck.", msg_id, folder, + ); + return false; + } + + // we do not know the message-id + // or the message-id is missing (in this case, we create one in the further process) + // or some other error happened + let show = match prefetch_should_download(context, &headers, show_emails).await { + Ok(show) => show, + Err(err) => { + warn!(context, "prefetch_should_download error: {}", err); + true + } + }; + + if !show { + info!( + context, + "Ignoring new message {} from \"{}\".", msg_id, folder, + ); + return false; + } + + true +} From 4330da232c86d8c12241e523d2c1b716626592da Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Mon, 25 May 2020 17:37:19 +0200 Subject: [PATCH 113/118] tweak async ffi-docs --- deltachat-ffi/deltachat.h | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index b6e2f74cc..723dd6723 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -46,10 +46,9 @@ typedef struct _dc_event_emitter dc_event_emitter_t; * so, in many situations you will do this in a thread: * * ~~~ - * dc_event_emitter_t* emitter = dc_get_event_emitter(context); - * - * void* event_handler(void* emitter) + * void* event_handler(void* context) * { + * dc_event_emitter_t* emitter = dc_get_event_emitter(context); * dc_event_t* event; * while ((event = dc_get_next_event(emitter)) != NULL) { * // use the event as needed, eg. dc_event_get_id() returns the type. @@ -60,7 +59,7 @@ typedef struct _dc_event_emitter dc_event_emitter_t; * } * * static pthread_t event_thread; - * pthread_create(&event_thread, NULL, event_handler, emitter); + * pthread_create(&event_thread, NULL, event_handler, context); * ~~~ * * The example above uses "pthreads", @@ -103,11 +102,11 @@ typedef struct _dc_event_emitter dc_event_emitter_t; * ~~~ * * dc_send_text_msg() returns immediately; - * the sending itself is done by a job in the smtp-thread you've defined above. + * the sending itself is done in the background. * If you check the testing address (bob) * and you should have received a normal email. * Answer this email in any email program with "Got it!" - * and the imap-thread you've create above will **receive the message**. + * and the IO you started above will **receive the message**. * * You can then **list all messages** of a chat as follow: * @@ -431,9 +430,7 @@ char* dc_get_oauth2_url (dc_context_t* context, const char* /** * Configure a context. - * For this purpose, the function creates a job - * that is executed in the IMAP-thread then; - * this requires to call dc_perform_imap_jobs() regularly. + * While configuration IO must not be started, if needed stop IO using dc_stop_io() first. * If the context is already configured, * this function will try to change the configuration. * @@ -500,6 +497,8 @@ int dc_is_configured (const dc_context_t* context); /** * Start job and IMAP/SMTP tasks. + * You must not call dc_start_io() if IO is already started, + * please check the current state using dc_is_io_running() first. * * @memberof dc_context_t * @param context The context object as created by dc_context_new(). @@ -1637,9 +1636,6 @@ dc_contact_t* dc_get_contact (dc_context_t* context, uint32_t co /** * Import/export things. - * For this purpose, the function creates a job that is executed in the IMAP-thread then; - * this requires to call dc_perform_imap_jobs() regularly. - * * What to do is defined by the _what_ parameter which may be one of the following: * * - **DC_IMEX_EXPORT_BACKUP** (11) - Export a backup to the directory given as `param1`. From 3db6d5a458bc70aad80809c75c4b9ba68e15abf0 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Tue, 26 May 2020 14:08:47 +0200 Subject: [PATCH 114/118] allow calls to start_io when already running and to stop_io when not running, only log a message in these cases --- deltachat-ffi/deltachat.h | 6 ++++-- src/context.rs | 10 +++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 723dd6723..766cee83c 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -497,8 +497,8 @@ int dc_is_configured (const dc_context_t* context); /** * Start job and IMAP/SMTP tasks. - * You must not call dc_start_io() if IO is already started, - * please check the current state using dc_is_io_running() first. + * If IO is already running, nothing happens. + * To check the current IO state, use dc_is_io_running(). * * @memberof dc_context_t * @param context The context object as created by dc_context_new(). @@ -518,6 +518,8 @@ int dc_is_io_running(const dc_context_t* context); /** * Stop job and IMAP/SMTP tasks and return when they are finished. + * If IO is not running, nothing happens. + * To check the current IO state, use dc_is_io_running(). * * @memberof dc_context_t * @param context The context object as created by dc_context_new(). diff --git a/src/context.rs b/src/context.rs index 2eafdb22d..ca5fcfcdc 100644 --- a/src/context.rs +++ b/src/context.rs @@ -138,7 +138,10 @@ impl Context { /// Starts the IO scheduler. pub async fn start_io(&self) { info!(self, "starting IO"); - assert!(!self.is_io_running().await, "context is already running"); + if self.is_io_running().await { + info!(self, "IO is already running"); + return; + } let l = &mut *self.inner.scheduler.write().await; l.start(self.clone()).await; @@ -152,6 +155,11 @@ impl Context { /// Stops the IO scheduler. pub async fn stop_io(&self) { info!(self, "stopping IO"); + if !self.is_io_running().await { + info!(self, "IO is not running"); + return; + } + self.inner.stop_io().await; } From bd903d8e8fa400dcdb78e001dbebc65925331801 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Tue, 26 May 2020 19:40:30 +0200 Subject: [PATCH 115/118] fix: avoid short smtp interruptions --- src/scheduler.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/scheduler.rs b/src/scheduler.rs index 358beefd0..042c51a25 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -255,11 +255,7 @@ async fn smtp_loop(ctx: Context, started: Sender<()>, smtp_handlers: SmtpConnect Ok(None) | Err(async_std::future::TimeoutError { .. }) => { info!(ctx, "smtp fake idle"); // Fake Idle - idle_interrupt_receiver - .recv() - .timeout(Duration::from_secs(5)) - .await - .ok(); + idle_interrupt_receiver.recv().await.ok(); } } } From 307357df70e8dcc36b0cd2c93725582f1b6529b7 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Tue, 26 May 2020 19:40:37 +0200 Subject: [PATCH 116/118] update simple exaple --- examples/simple.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/examples/simple.rs b/examples/simple.rs index cdfeeea89..2d37769ad 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -5,6 +5,7 @@ use deltachat::chatlist::*; use deltachat::config; use deltachat::contact::*; use deltachat::context::*; +use deltachat::message::Message; use deltachat::Event; fn cb(event: Event) { @@ -71,20 +72,24 @@ async fn main() { .unwrap(); let chat_id = chat::create_by_contact_id(&ctx, contact_id).await.unwrap(); - for i in 0..2 { + for i in 0..1 { + log::info!("sending message {}", i); chat::send_text_msg(&ctx, chat_id, format!("Hi, here is my {}nth message!", i)) .await .unwrap(); } + // wait for the message to be sent out + async_std::task::sleep(std::time::Duration::from_secs(1)).await; + log::info!("fetching chats.."); let chats = Chatlist::try_load(&ctx, 0, None, None).await.unwrap(); for i in 0..chats.len() { - let summary = chats.get_summary(&ctx, 0, None).await; - let text1 = summary.get_text1(); - let text2 = summary.get_text2(); - log::info!("chat: {} - {:?} - {:?}", i, text1, text2,); + let msg = Message::load_from_db(&ctx, chats.get_msg_id(i).unwrap()) + .await + .unwrap(); + log::info!("[{}] msg: {:?}", i, msg); } log::info!("stopping"); From 9f7f387540515ccc7c1d1712b620ad61bccedb92 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 27 May 2020 15:05:29 +0200 Subject: [PATCH 117/118] examples: fix blocking the scheduler in the repl --- examples/repl/main.rs | 83 ++++++++++++++++++++++++------------------- 1 file changed, 47 insertions(+), 36 deletions(-) diff --git a/examples/repl/main.rs b/examples/repl/main.rs index e49844463..eaa05be30 100644 --- a/examples/repl/main.rs +++ b/examples/repl/main.rs @@ -290,48 +290,59 @@ async fn start(args: Vec) -> Result<(), Error> { .edit_mode(EditMode::Emacs) .output_stream(OutputStreamType::Stdout) .build(); - let h = DcHelper { - completer: FilenameCompleter::new(), - highlighter: MatchingBracketHighlighter::new(), - hinter: HistoryHinter {}, - }; - let mut rl = Editor::with_config(config); - rl.set_helper(Some(h)); - rl.bind_sequence(KeyPress::Meta('N'), Cmd::HistorySearchForward); - rl.bind_sequence(KeyPress::Meta('P'), Cmd::HistorySearchBackward); - if rl.load_history(".dc-history.txt").is_err() { - println!("No previous history."); - } - let mut selected_chat = ChatId::default(); + let (reader_s, reader_r) = async_std::sync::channel(100); + let input_loop = async_std::task::spawn_blocking(move || { + let h = DcHelper { + completer: FilenameCompleter::new(), + highlighter: MatchingBracketHighlighter::new(), + hinter: HistoryHinter {}, + }; + let mut rl = Editor::with_config(config); + rl.set_helper(Some(h)); + rl.bind_sequence(KeyPress::Meta('N'), Cmd::HistorySearchForward); + rl.bind_sequence(KeyPress::Meta('P'), Cmd::HistorySearchBackward); + if rl.load_history(".dc-history.txt").is_err() { + println!("No previous history."); + } - loop { - let p = "> "; - let readline = rl.readline(&p); - match readline { - Ok(line) => { - // TODO: ignore "set mail_pw" - rl.add_history_entry(line.as_str()); - match handle_cmd(line.trim(), context.clone(), &mut selected_chat).await { - Ok(ExitResult::Continue) => {} - Ok(ExitResult::Exit) => break, - Err(err) => println!("Error: {}", err), + loop { + let p = "> "; + let readline = rl.readline(&p); + + match readline { + Ok(line) => { + // TODO: ignore "set mail_pw" + rl.add_history_entry(line.as_str()); + async_std::task::block_on(reader_s.send(line)); + } + Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => { + println!("Exiting..."); + drop(reader_s); + break; + } + Err(err) => { + println!("Error: {}", err); + drop(reader_s); + break; } } - Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => { - println!("Exiting..."); - context.stop_io().await; - break; - } - Err(err) => { - println!("Error: {}", err); - break; - } + } + + rl.save_history(".dc-history.txt")?; + println!("history saved"); + Ok::<_, Error>(()) + }); + + while let Ok(line) = reader_r.recv().await { + match handle_cmd(line.trim(), context.clone(), &mut selected_chat).await { + Ok(ExitResult::Continue) => {} + Ok(ExitResult::Exit) => break, + Err(err) => println!("Error: {}", err), } } - - rl.save_history(".dc-history.txt")?; - println!("history saved"); + context.stop_io().await; + input_loop.await?; Ok(()) } From 6100a23e80c7d7cdf48a0f279ca3e88184db852c Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 27 May 2020 15:13:29 +0200 Subject: [PATCH 118/118] fix: avoid lock for probe_network, avoiding deadlock on startup Closes #1532 --- src/config.rs | 6 +- src/context.rs | 6 +- src/imap/idle.rs | 130 ++++++++++++++++++++++++--------------- src/imap/mod.rs | 4 +- src/job.rs | 70 +++++++++++++-------- src/scheduler.rs | 155 ++++++++++++++++++++++------------------------- 6 files changed, 205 insertions(+), 166 deletions(-) diff --git a/src/config.rs b/src/config.rs index 9cc1f4edd..55c465155 100644 --- a/src/config.rs +++ b/src/config.rs @@ -199,17 +199,17 @@ impl Context { } Config::InboxWatch => { let ret = self.sql.set_raw_config(self, key, value).await; - self.interrupt_inbox().await; + self.interrupt_inbox(false).await; ret } Config::SentboxWatch => { let ret = self.sql.set_raw_config(self, key, value).await; - self.interrupt_sentbox().await; + self.interrupt_sentbox(false).await; ret } Config::MvboxWatch => { let ret = self.sql.set_raw_config(self, key, value).await; - self.interrupt_mvbox().await; + self.interrupt_mvbox(false).await; ret } Config::Selfstatus => { diff --git a/src/context.rs b/src/context.rs index 2eafdb22d..a033146af 100644 --- a/src/context.rs +++ b/src/context.rs @@ -140,8 +140,10 @@ impl Context { info!(self, "starting IO"); assert!(!self.is_io_running().await, "context is already running"); - let l = &mut *self.inner.scheduler.write().await; - l.start(self.clone()).await; + { + let l = &mut *self.inner.scheduler.write().await; + l.start(self.clone()).await; + } } /// Returns if the IO scheduler is running. diff --git a/src/imap/idle.rs b/src/imap/idle.rs index ed762fc0f..e6b3ea621 100644 --- a/src/imap/idle.rs +++ b/src/imap/idle.rs @@ -34,7 +34,7 @@ impl Imap { self.config.can_idle } - pub async fn idle(&mut self, context: &Context, watch_folder: Option) -> Result<()> { + pub async fn idle(&mut self, context: &Context, watch_folder: Option) -> Result { use futures::future::FutureExt; if !self.can_idle() { @@ -46,6 +46,7 @@ impl Imap { let session = self.session.take(); let timeout = Duration::from_secs(23 * 60); + let mut probe_network = false; if let Some(session) = session { let mut handle = session.idle(); @@ -55,6 +56,11 @@ impl Imap { let (idle_wait, interrupt) = handle.wait_with_timeout(timeout); + enum Event { + IdleResponse(IdleResponse), + Interrupt(bool), + } + if self.skip_next_idle_wait { // interrupt_idle has happened before we // provided self.interrupt @@ -65,23 +71,27 @@ impl Imap { info!(context, "Idle wait was skipped"); } else { info!(context, "Idle entering wait-on-remote state"); - let fut = idle_wait.race( - self.idle_interrupt - .recv() - .map(|_| Ok(IdleResponse::ManualInterrupt)), + let fut = idle_wait.map(|ev| ev.map(Event::IdleResponse)).race( + self.idle_interrupt.recv().map(|probe_network| { + Ok(Event::Interrupt(probe_network.unwrap_or_default())) + }), ); match fut.await { - Ok(IdleResponse::NewData(_)) => { + Ok(Event::IdleResponse(IdleResponse::NewData(_))) => { info!(context, "Idle has NewData"); } // TODO: idle_wait does not distinguish manual interrupts // from Timeouts if we would know it's a Timeout we could bail // directly and reconnect . - Ok(IdleResponse::Timeout) => { + Ok(Event::IdleResponse(IdleResponse::Timeout)) => { info!(context, "Idle-wait timeout or interruption"); } - Ok(IdleResponse::ManualInterrupt) => { + Ok(Event::IdleResponse(IdleResponse::ManualInterrupt)) => { + info!(context, "Idle wait was interrupted"); + } + Ok(Event::Interrupt(probe)) => { + probe_network = probe; info!(context, "Idle wait was interrupted"); } Err(err) => { @@ -115,16 +125,26 @@ impl Imap { } } - Ok(()) + Ok(probe_network) } - pub(crate) async fn fake_idle(&mut self, context: &Context, watch_folder: Option) { + pub(crate) async fn fake_idle( + &mut self, + context: &Context, + watch_folder: Option, + ) -> bool { // Idle using polling. This is also needed if we're not yet configured - // in this case, we're waiting for a configure job (and an interrupt). let fake_idle_start_time = SystemTime::now(); info!(context, "IMAP-fake-IDLEing..."); + // Do not poll, just wait for an interrupt when no folder is passed in. + if watch_folder.is_none() { + return self.idle_interrupt.recv().await.unwrap_or_default(); + } + + let mut probe_network = false; if self.skip_next_idle_wait { // interrupt_idle has happened before we // provided self.interrupt @@ -135,53 +155,61 @@ impl Imap { // TODO: grow sleep durations / make them more flexible let mut interval = async_std::stream::interval(Duration::from_secs(60)); + enum Event { + Tick, + Interrupt(bool), + } // loop until we are interrupted or if we fetched something - loop { - use futures::future::FutureExt; - match interval - .next() - .race(self.idle_interrupt.recv().map(|_| None)) - .await - { - Some(_) => { - // try to connect with proper login params - // (setup_handle_if_needed might not know about them if we - // never successfully connected) - if let Err(err) = self.connect_configured(context).await { - warn!(context, "fake_idle: could not connect: {}", err); - continue; - } - if self.config.can_idle { - // we only fake-idled because network was gone during IDLE, probably - break; - } - info!(context, "fake_idle is connected"); - // we are connected, let's see if fetching messages results - // in anything. If so, we behave as if IDLE had data but - // will have already fetched the messages so perform_*_fetch - // will not find any new. + probe_network = + loop { + use futures::future::FutureExt; + match interval + .next() + .map(|_| Event::Tick) + .race(self.idle_interrupt.recv().map(|probe_network| { + Event::Interrupt(probe_network.unwrap_or_default()) + })) + .await + { + Event::Tick => { + // try to connect with proper login params + // (setup_handle_if_needed might not know about them if we + // never successfully connected) + if let Err(err) = self.connect_configured(context).await { + warn!(context, "fake_idle: could not connect: {}", err); + continue; + } + if self.config.can_idle { + // we only fake-idled because network was gone during IDLE, probably + break false; + } + info!(context, "fake_idle is connected"); + // we are connected, let's see if fetching messages results + // in anything. If so, we behave as if IDLE had data but + // will have already fetched the messages so perform_*_fetch + // will not find any new. - if let Some(ref watch_folder) = watch_folder { - match self.fetch_new_messages(context, watch_folder).await { - Ok(res) => { - info!(context, "fetch_new_messages returned {:?}", res); - if res { - break; + if let Some(ref watch_folder) = watch_folder { + match self.fetch_new_messages(context, watch_folder).await { + Ok(res) => { + info!(context, "fetch_new_messages returned {:?}", res); + if res { + break false; + } + } + Err(err) => { + error!(context, "could not fetch from folder: {}", err); + self.trigger_reconnect() } - } - Err(err) => { - error!(context, "could not fetch from folder: {}", err); - self.trigger_reconnect() } } } + Event::Interrupt(probe_network) => { + // Interrupt + break probe_network; + } } - None => { - // Interrupt - break; - } - } - } + }; } info!( @@ -193,5 +221,7 @@ impl Imap { .as_millis() as f64 / 1000., ); + + probe_network } } diff --git a/src/imap/mod.rs b/src/imap/mod.rs index 6032fb873..216689ff6 100644 --- a/src/imap/mod.rs +++ b/src/imap/mod.rs @@ -109,7 +109,7 @@ const SELECT_ALL: &str = "1:*"; #[derive(Debug)] pub struct Imap { - idle_interrupt: Receiver<()>, + idle_interrupt: Receiver, config: ImapConfig, session: Option, connected: bool, @@ -181,7 +181,7 @@ impl Default for ImapConfig { } impl Imap { - pub fn new(idle_interrupt: Receiver<()>) -> Self { + pub fn new(idle_interrupt: Receiver) -> Self { Imap { idle_interrupt, config: Default::default(), diff --git a/src/job.rs b/src/job.rs index e8f6f0d25..7249cb0e5 100644 --- a/src/job.rs +++ b/src/job.rs @@ -1028,13 +1028,15 @@ pub async fn add(context: &Context, job: Job) { | Action::DeleteMsgOnImap | Action::MarkseenMsgOnImap | Action::MoveMsg => { - context.interrupt_inbox().await; + info!(context, "interrupt: imap"); + context.interrupt_inbox(false).await; } Action::MaybeSendLocations | Action::MaybeSendLocationsEnded | Action::SendMdn | Action::SendMsgToSmtp => { - context.interrupt_smtp().await; + info!(context, "interrupt: smtp"); + context.interrupt_smtp(false).await; } } } @@ -1088,13 +1090,13 @@ LIMIT 1; .sql .query_row_optional(query, params.clone(), |row| { let job = Job { - job_id: row.get(0)?, - action: row.get(1)?, - foreign_id: row.get(2)?, - desired_timestamp: row.get(5)?, - added_timestamp: row.get(4)?, - tries: row.get(6)?, - param: row.get::<_, String>(3)?.parse().unwrap_or_default(), + job_id: row.get("id")?, + action: row.get("action")?, + foreign_id: row.get("foreign_id")?, + desired_timestamp: row.get("desired_timestamp")?, + added_timestamp: row.get("added_timestamp")?, + tries: row.get("tries")?, + param: row.get::<_, String>("param")?.parse().unwrap_or_default(), pending_error: None, }; @@ -1104,8 +1106,9 @@ LIMIT 1; match job_res { Ok(job) => break job, - Err(_) => { + Err(err) => { // Remove invalid job from the DB + info!(context, "cleaning up job, because of {}", err); // TODO: improve by only doing a single query match context @@ -1116,7 +1119,7 @@ LIMIT 1; Ok(id) => { context .sql - .execute("DELETE FROM jobs WHERE id=?", paramsv![id]) + .execute("DELETE FROM jobs WHERE id=?;", paramsv![id]) .await .ok(); } @@ -1129,21 +1132,26 @@ LIMIT 1; } }; - if thread == Thread::Imap { - if let Some(job) = job { - if job.action < Action::DeleteMsgOnImap { - load_imap_deletion_job(context) - .await - .unwrap_or_default() - .or(Some(job)) - } else { - Some(job) - } - } else { - load_imap_deletion_job(context).await.unwrap_or_default() + match thread { + Thread::Unknown => { + error!(context, "unknown thread for job"); + None } - } else { - job + Thread::Imap => { + if let Some(job) = job { + if job.action < Action::DeleteMsgOnImap { + load_imap_deletion_job(context) + .await + .unwrap_or_default() + .or(Some(job)) + } else { + Some(job) + } + } else { + load_imap_deletion_job(context).await.unwrap_or_default() + } + } + Thread::Smtp => job, } } @@ -1175,7 +1183,7 @@ mod tests { } #[async_std::test] - async fn test_load_next_job() { + async fn test_load_next_job_two() { // We want to ensure that loading jobs skips over jobs which // fails to load from the database instead of failing to load // all jobs. @@ -1188,4 +1196,14 @@ mod tests { let jobs = load_next(&t.ctx, Thread::from(Action::MoveMsg), false).await; assert!(jobs.is_some()); } + + #[async_std::test] + async fn test_load_next_job_one() { + let t = dummy_context().await; + + insert_job(&t.ctx, 1).await; + + let jobs = load_next(&t.ctx, Thread::from(Action::MoveMsg), false).await; + assert!(jobs.is_some()); + } } diff --git a/src/scheduler.rs b/src/scheduler.rs index 042c51a25..e4076a6c4 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -2,8 +2,6 @@ use async_std::prelude::*; use async_std::sync::{channel, Receiver, Sender}; use async_std::task; -use std::time::Duration; - use crate::context::Context; use crate::imap::Imap; use crate::job::{self, Thread}; @@ -25,30 +23,45 @@ pub(crate) enum Scheduler { sentbox_handle: Option>, smtp: SmtpConnectionState, smtp_handle: Option>, - probe_network: bool, }, } impl Context { /// Indicate that the network likely has come back. pub async fn maybe_network(&self) { - self.scheduler.write().await.maybe_network().await; + self.scheduler.read().await.maybe_network().await; } - pub(crate) async fn interrupt_inbox(&self) { - self.scheduler.read().await.interrupt_inbox().await; + pub(crate) async fn interrupt_inbox(&self, probe_network: bool) { + self.scheduler + .read() + .await + .interrupt_inbox(probe_network) + .await; } - pub(crate) async fn interrupt_sentbox(&self) { - self.scheduler.read().await.interrupt_sentbox().await; + pub(crate) async fn interrupt_sentbox(&self, probe_network: bool) { + self.scheduler + .read() + .await + .interrupt_sentbox(probe_network) + .await; } - pub(crate) async fn interrupt_mvbox(&self) { - self.scheduler.read().await.interrupt_mvbox().await; + pub(crate) async fn interrupt_mvbox(&self, probe_network: bool) { + self.scheduler + .read() + .await + .interrupt_mvbox(probe_network) + .await; } - pub(crate) async fn interrupt_smtp(&self) { - self.scheduler.read().await.interrupt_smtp().await; + pub(crate) async fn interrupt_smtp(&self, probe_network: bool) { + self.scheduler + .read() + .await + .interrupt_smtp(probe_network) + .await; } } @@ -73,26 +86,23 @@ async fn inbox_loop(ctx: Context, started: Sender<()>, inbox_handlers: ImapConne // track number of continously executed jobs let mut jobs_loaded = 0; + let mut probe_network = false; loop { - let probe_network = ctx.scheduler.read().await.get_probe_network(); - match job::load_next(&ctx, Thread::Imap, probe_network) - .timeout(Duration::from_millis(200)) - .await - { - Ok(Some(job)) if jobs_loaded <= 20 => { + match job::load_next(&ctx, Thread::Imap, probe_network).await { + Some(job) if jobs_loaded <= 20 => { jobs_loaded += 1; job::perform_job(&ctx, job::Connection::Inbox(&mut connection), job).await; - ctx.scheduler.write().await.set_probe_network(false); + probe_network = false; } - Ok(Some(job)) => { + Some(job) => { // Let the fetch run, but return back to the job afterwards. info!(ctx, "postponing imap-job {} to run fetch...", job); jobs_loaded = 0; fetch(&ctx, &mut connection).await; } - Ok(None) | Err(async_std::future::TimeoutError { .. }) => { + None => { jobs_loaded = 0; - fetch_idle(&ctx, &mut connection).await; + probe_network = fetch_idle(&ctx, &mut connection).await; } } } @@ -126,7 +136,7 @@ async fn fetch(ctx: &Context, connection: &mut Imap) { } } -async fn fetch_idle(ctx: &Context, connection: &mut Imap) { +async fn fetch_idle(ctx: &Context, connection: &mut Imap) -> bool { match get_watch_folder(&ctx, "configured_inbox_folder").await { Some(watch_folder) => { // fetch @@ -144,14 +154,15 @@ async fn fetch_idle(ctx: &Context, connection: &mut Imap) { .await .unwrap_or_else(|err| { error!(ctx, "{}", err); - }); + false + }) } else { - connection.fake_idle(&ctx, Some(watch_folder)).await; + connection.fake_idle(&ctx, Some(watch_folder)).await } } None => { warn!(ctx, "Can not watch inbox folder, not set"); - connection.fake_idle(&ctx, None).await; + connection.fake_idle(&ctx, None).await } } } @@ -199,6 +210,7 @@ async fn simple_imap_loop( .await .unwrap_or_else(|err| { error!(ctx, "{}", err); + false }); } else { connection.fake_idle(&ctx, Some(watch_folder)).await; @@ -210,7 +222,7 @@ async fn simple_imap_loop( "No watch folder found for {}, skipping", folder.as_ref() ); - connection.fake_idle(&ctx, None).await + connection.fake_idle(&ctx, None).await; } } } @@ -241,21 +253,20 @@ async fn smtp_loop(ctx: Context, started: Sender<()>, smtp_handlers: SmtpConnect let fut = async move { started.send(()).await; let ctx = ctx1; + + let mut probe_network = false; loop { - let probe_network = ctx.scheduler.read().await.get_probe_network(); - match job::load_next(&ctx, Thread::Smtp, probe_network) - .timeout(Duration::from_millis(200)) - .await - { - Ok(Some(job)) => { + match job::load_next(&ctx, Thread::Smtp, probe_network).await { + Some(job) => { info!(ctx, "executing smtp job"); job::perform_job(&ctx, job::Connection::Smtp(&mut connection), job).await; - ctx.scheduler.write().await.set_probe_network(false); + probe_network = false; } - Ok(None) | Err(async_std::future::TimeoutError { .. }) => { - info!(ctx, "smtp fake idle"); + None => { // Fake Idle - idle_interrupt_receiver.recv().await.ok(); + info!(ctx, "smtp fake idle - started"); + probe_network = idle_interrupt_receiver.recv().await.unwrap_or_default(); + info!(ctx, "smtp fake idle - interrupted") } } } @@ -284,7 +295,6 @@ impl Scheduler { mvbox, sentbox, smtp, - probe_network: false, inbox_handle: None, mvbox_handle: None, sentbox_handle: None, @@ -349,58 +359,39 @@ impl Scheduler { info!(ctx, "scheduler is running"); } - fn set_probe_network(&mut self, val: bool) { - match self { - Scheduler::Running { - ref mut probe_network, - .. - } => { - *probe_network = val; - } - _ => panic!("set_probe_network can only be called when running"), - } - } - - fn get_probe_network(&self) -> bool { - match self { - Scheduler::Running { probe_network, .. } => *probe_network, - _ => panic!("get_probe_network can only be called when running"), - } - } - - async fn maybe_network(&mut self) { + async fn maybe_network(&self) { if !self.is_running() { return; } - self.set_probe_network(true); - self.interrupt_inbox() - .join(self.interrupt_mvbox()) - .join(self.interrupt_sentbox()) - .join(self.interrupt_smtp()) + + self.interrupt_inbox(true) + .join(self.interrupt_mvbox(true)) + .join(self.interrupt_sentbox(true)) + .join(self.interrupt_smtp(true)) .await; } - async fn interrupt_inbox(&self) { + async fn interrupt_inbox(&self, probe_network: bool) { if let Scheduler::Running { ref inbox, .. } = self { - inbox.interrupt().await; + inbox.interrupt(probe_network).await; } } - async fn interrupt_mvbox(&self) { + async fn interrupt_mvbox(&self, probe_network: bool) { if let Scheduler::Running { ref mvbox, .. } = self { - mvbox.interrupt().await; + mvbox.interrupt(probe_network).await; } } - async fn interrupt_sentbox(&self) { + async fn interrupt_sentbox(&self, probe_network: bool) { if let Scheduler::Running { ref sentbox, .. } = self { - sentbox.interrupt().await; + sentbox.interrupt(probe_network).await; } } - async fn interrupt_smtp(&self) { + async fn interrupt_smtp(&self, probe_network: bool) { if let Scheduler::Running { ref smtp, .. } = self { - smtp.interrupt().await; + smtp.interrupt(probe_network).await; } } @@ -469,7 +460,7 @@ struct ConnectionState { /// Channel to interrupt the whole connection. stop_sender: Sender<()>, /// Channel to interrupt idle. - idle_interrupt_sender: Sender<()>, + idle_interrupt_sender: Sender, } impl ConnectionState { @@ -481,11 +472,9 @@ impl ConnectionState { self.shutdown_receiver.recv().await.ok(); } - async fn interrupt(&self) { - if !self.idle_interrupt_sender.is_full() { - // Use try_send to avoid blocking on interrupts. - self.idle_interrupt_sender.send(()).await; - } + async fn interrupt(&self, probe_network: bool) { + // Use try_send to avoid blocking on interrupts. + self.idle_interrupt_sender.try_send(probe_network).ok(); } } @@ -519,8 +508,8 @@ impl SmtpConnectionState { } /// Interrupt any form of idle. - async fn interrupt(&self) { - self.state.interrupt().await; + async fn interrupt(&self, probe_network: bool) { + self.state.interrupt(probe_network).await; } /// Shutdown this connection completely. @@ -534,7 +523,7 @@ struct SmtpConnectionHandlers { connection: Smtp, stop_receiver: Receiver<()>, shutdown_sender: Sender<()>, - idle_interrupt_receiver: Receiver<()>, + idle_interrupt_receiver: Receiver, } #[derive(Debug)] @@ -546,8 +535,8 @@ impl ImapConnectionState { /// Construct a new connection. fn new() -> (Self, ImapConnectionHandlers) { let (stop_sender, stop_receiver) = channel(1); - let (idle_interrupt_sender, idle_interrupt_receiver) = channel(1); let (shutdown_sender, shutdown_receiver) = channel(1); + let (idle_interrupt_sender, idle_interrupt_receiver) = channel(1); let handlers = ImapConnectionHandlers { connection: Imap::new(idle_interrupt_receiver), @@ -567,8 +556,8 @@ impl ImapConnectionState { } /// Interrupt any form of idle. - async fn interrupt(&self) { - self.state.interrupt().await; + async fn interrupt(&self, probe_network: bool) { + self.state.interrupt(probe_network).await; } /// Shutdown this connection completely.