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<()>