Compare commits

...

1 Commits

Author SHA1 Message Date
Simon Laux
3ca294c434 feat: disable background io per account, available apis in rust and jsonrpc
api: rust: add `set_disable_background_io` and `get_disable_background_io` to a `Accounts` (the account manager)

api: jsonrpc: add `set_disable_background_io` method and `Account::Configured.background_io_disabled` property

api: select account now starts and stops io for "background disabled" accounts

This is partly a port of the desktop "disable sync all" option, the other part is making it per account.
So you can define that an account should be excluded from background syncing. This can be useful for advanced users that want to control their network traffic (don't connect a specific account in the background unless it is actively selected, like when the network is monitored and you want to hide your other accounts by disabling their background syncing.)
2024-03-02 01:04:03 +01:00
3 changed files with 133 additions and 32 deletions

View File

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

View File

@@ -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<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
))
}
}
}

View File

@@ -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<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() {
@@ -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<bool> {
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 {