Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: disable background io per account, available apis in rust and jsonrpc #5314

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 25 additions & 14 deletions deltachat-jsonrpc/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,16 +210,16 @@ impl CommandApi {
/// Get a list of all configured accounts.
async fn get_all_accounts(&self) -> Result<Vec<Account>> {
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(())
Expand All @@ -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()
Expand All @@ -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
// ---------------------------------------------
Expand All @@ -265,15 +283,8 @@ impl CommandApi {

/// Get top-level info for an account.
async fn get_account_info(&self, account_id: u32) -> Result<Account> {
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
Expand Down
48 changes: 31 additions & 17 deletions deltachat-jsonrpc/src/api/types/account.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use anyhow::Result;
use anyhow::{anyhow, Result};
use deltachat::config::Config;
use deltachat::contact::{Contact, ContactId};
use serde::Serialize;
Expand All @@ -17,29 +17,43 @@ pub enum Account {
// size: u32,
profile_image: Option<String>, // 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<Self> {
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<Self> {
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
))
}
}
}
78 changes: 77 additions & 1 deletion src/accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}

Expand Down Expand Up @@ -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;
}
}
}

Expand All @@ -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<bool> {
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() {
Expand All @@ -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 {
Expand All @@ -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),
)
Expand All @@ -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
Expand Down Expand Up @@ -558,6 +605,7 @@ impl Config {
id,
dir: target_dir,
uuid,
disable_background_io: false,
});
self.inner.next_id += 1;
id
Expand Down Expand Up @@ -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<bool> {
Some(self.get_account(id)?.disable_background_io)
}
}

/// Spend up to 1 minute trying to do the operation.
Expand Down Expand Up @@ -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 {
Expand Down
Loading