diff --git a/deltachat-jsonrpc/src/api.rs b/deltachat-jsonrpc/src/api.rs index 4d100b27d3..afae00a706 100644 --- a/deltachat-jsonrpc/src/api.rs +++ b/deltachat-jsonrpc/src/api.rs @@ -210,16 +210,16 @@ impl CommandApi { /// Get a list of all configured accounts. async fn get_all_accounts(&self) -> Result> { let mut accounts = Vec::new(); - for id in self.accounts.read().await.get_all() { - let context_option = self.accounts.read().await.get_account(id); - if let Some(ctx) = context_option { - accounts.push(Account::from_context(&ctx, id).await?) - } + let manager = self.accounts.read().await; + for id in manager.get_all() { + accounts.push(Account::load(&manager, id).await?) } Ok(accounts) } /// Starts background tasks for all accounts. + /// + /// Acounts with `disable_background_io` are not started, unless the account is the selected one async fn start_io_for_all_accounts(&self) -> Result<()> { self.accounts.write().await.start_io().await; Ok(()) @@ -236,6 +236,8 @@ impl CommandApi { /// The `AccountsBackgroundFetchDone` event is emitted at the end even in case of timeout. /// Process all events until you get this one and you can safely return to the background /// without forgetting to create notifications caused by timing race conditions. + /// + /// Acounts with `disable_background_io` are not fetched async fn accounts_background_fetch(&self, timeout_in_seconds: f64) -> Result<()> { self.accounts .write() @@ -245,6 +247,22 @@ impl CommandApi { Ok(()) } + /// Set disable_background_io for an account, when enabled, + /// io is stopped unless the account is selected and background fetch is also disabled for the account + /// + /// This automatically stops/starts io when account is in the background + pub async fn set_disable_background_io( + &self, + account_id: u32, + disable_background_io: bool, + ) -> Result<()> { + self.accounts + .write() + .await + .set_disable_background_io(account_id, disable_background_io) + .await + } + // --------------------------------------------- // Methods that work on individual accounts // --------------------------------------------- @@ -265,15 +283,8 @@ impl CommandApi { /// Get top-level info for an account. async fn get_account_info(&self, account_id: u32) -> Result { - let context_option = self.accounts.read().await.get_account(account_id); - if let Some(ctx) = context_option { - Ok(Account::from_context(&ctx, account_id).await?) - } else { - Err(anyhow!( - "account with id {} doesn't exist anymore", - account_id - )) - } + let manager = &self.accounts.read().await; + Account::load(manager, account_id).await } /// Get the combined filesize of an account in bytes diff --git a/deltachat-jsonrpc/src/api/types/account.rs b/deltachat-jsonrpc/src/api/types/account.rs index b2909109db..9312a6e631 100644 --- a/deltachat-jsonrpc/src/api/types/account.rs +++ b/deltachat-jsonrpc/src/api/types/account.rs @@ -1,4 +1,4 @@ -use anyhow::Result; +use anyhow::{anyhow, Result}; use deltachat::config::Config; use deltachat::contact::{Contact, ContactId}; use serde::Serialize; @@ -17,29 +17,43 @@ pub enum Account { // size: u32, profile_image: Option, // TODO: This needs to be converted to work with blob http server. color: String, + + /// Account IO is disabled when this account is not selected + /// + /// this means IO is stopped unless this account is selected + /// and background fetch is also disabled for this account + background_io_disabled: bool, }, #[serde(rename_all = "camelCase")] Unconfigured { id: u32 }, } impl Account { - pub async fn from_context(ctx: &deltachat::context::Context, id: u32) -> Result { - if ctx.is_configured().await? { - let display_name = ctx.get_config(Config::Displayname).await?; - let addr = ctx.get_config(Config::Addr).await?; - let profile_image = ctx.get_config(Config::Selfavatar).await?; - let color = color_int_to_hex_string( - Contact::get_by_id(ctx, ContactId::SELF).await?.get_color(), - ); - Ok(Account::Configured { - id, - display_name, - addr, - profile_image, - color, - }) + pub async fn load(accounts: &deltachat::accounts::Accounts, id: u32) -> Result { + if let Some(ctx) = &accounts.get_account(id) { + if ctx.is_configured().await? { + let display_name = ctx.get_config(Config::Displayname).await?; + let addr = ctx.get_config(Config::Addr).await?; + let profile_image = ctx.get_config(Config::Selfavatar).await?; + let color = color_int_to_hex_string( + Contact::get_by_id(ctx, ContactId::SELF).await?.get_color(), + ); + Ok(Account::Configured { + id: ctx.get_id(), + display_name, + addr, + profile_image, + color, + background_io_disabled: accounts.get_disable_background_io(id).unwrap_or(false), + }) + } else { + Ok(Account::Unconfigured { id }) + } } else { - Ok(Account::Unconfigured { id }) + Err(anyhow!( + "account with id {} doesn't exist anymore", + id + )) } } } diff --git a/src/accounts.rs b/src/accounts.rs index 626d279eff..c164c3c22a 100644 --- a/src/accounts.rs +++ b/src/accounts.rs @@ -108,8 +108,21 @@ impl Accounts { /// Selects the given account. pub async fn select_account(&mut self, id: u32) -> Result<()> { + let previous_account_id = self.config.get_selected_account(); + if let Some(true) = self.config.get_disable_background_io(previous_account_id) { + if let Some(previous_account_ctx) = self.get_account(previous_account_id) { + previous_account_ctx.stop_io().await; + } + } + self.config.select_account(id).await?; + if let Some(true) = self.config.get_disable_background_io(id) { + if let Some(previous_account_ctx) = self.get_account(id) { + previous_account_ctx.start_io().await; + } + } + Ok(()) } @@ -264,7 +277,9 @@ impl Accounts { /// Starts background tasks such as IMAP and SMTP loops for all accounts. pub async fn start_io(&mut self) { for account in self.accounts.values_mut() { - account.start_io().await; + if let Some(false) = self.config.get_disable_background_io(account.id) { + account.start_io().await; + } } } @@ -278,6 +293,33 @@ impl Accounts { } } + /// Set disable_background_io for an account, when enabled, + /// io is stopped unless the account is selected and background fetch is also disabled for the account + /// + /// This automatically stops/starts io when account is in the background + pub async fn set_disable_background_io( + &mut self, + id: u32, + disable_background_io: bool, + ) -> Result<()> { + self.config + .set_disable_background_io(id, disable_background_io) + .await?; + if let Some(account) = self.get_account(id) { + if disable_background_io { + account.stop_io().await; + } else { + account.start_io().await; + } + } + Ok(()) + } + + // Checks if background io is disabled + pub fn get_disable_background_io(&self, id: u32) -> Option { + self.config.get_disable_background_io(id) + } + /// Notifies all accounts that the network may have become available. pub async fn maybe_network(&self) { for account in self.accounts.values() { @@ -296,6 +338,8 @@ impl Accounts { /// /// This is an auxiliary function and not part of public API. /// Use [Accounts::background_fetch] instead. + /// + /// Acounts with `disable_background_io` are not fetched async fn background_fetch_without_timeout(&self) { async fn background_fetch_and_log_error(account: Context) { if let Err(error) = account.background_fetch().await { @@ -306,6 +350,7 @@ impl Accounts { join_all( self.accounts .values() + .filter(|account| self.config.get_disable_background_io(account.id) != Some(true)) .cloned() .map(background_fetch_and_log_error), ) @@ -317,6 +362,8 @@ impl Accounts { /// The `AccountsBackgroundFetchDone` event is emitted at the end, /// process all events until you get this one and you can safely return to the background /// without forgetting to create notifications caused by timing race conditions. + /// + /// Acounts with `disable_background_io` are not fetched pub async fn background_fetch(&self, timeout: std::time::Duration) { if let Err(_err) = tokio::time::timeout(timeout, self.background_fetch_without_timeout()).await @@ -558,6 +605,7 @@ impl Config { id, dir: target_dir, uuid, + disable_background_io: false, }); self.inner.next_id += 1; id @@ -620,6 +668,28 @@ impl Config { self.sync().await?; Ok(()) } + + pub(crate) async fn set_disable_background_io(&mut self, id: u32, value: bool) -> Result<()> { + let position = self + .inner + .accounts + .iter() + .position(|e| e.id == id) + .context("account not found")?; + self.inner + .accounts + .get_mut(position) + .context("account not found")? + .disable_background_io = value; + + self.sync().await?; + Ok(()) + } + + // Checks if background io is disabled + pub fn get_disable_background_io(&self, id: u32) -> Option { + Some(self.get_account(id)?.disable_background_io) + } } /// Spend up to 1 minute trying to do the operation. @@ -666,6 +736,12 @@ struct AccountConfig { /// Universally unique account identifier. pub uuid: Uuid, + + /// Disable account io when it is not selected + /// + /// this means io is stopped unless the account is selected + /// and background fetch is also disabled for the account + pub disable_background_io: bool, } impl AccountConfig {