diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index b540cc5339..322f4cd98b 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -7304,12 +7304,6 @@ void dc_event_unref(dc_event_t* event); /// Used as a headline in the connectivity view. #define DC_STR_OUTGOING_MESSAGES 104 -/// "Storage on %1$s" -/// -/// Used as a headline in the connectivity view. -/// -/// `%1$s` will be replaced by the domain of the configured e-mail address. -#define DC_STR_STORAGE_ON_DOMAIN 105 /// @deprecated Deprecated 2022-04-16, this string is no longer needed. #define DC_STR_ONE_MOMENT 106 diff --git a/src/config.rs b/src/config.rs index 62feb6bca1..b4d65d798d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -944,7 +944,7 @@ impl Context { /// This should only be used by test code and during configure. #[cfg(test)] // AEAP is disabled, but there are still tests for it pub(crate) async fn set_primary_self_addr(&self, primary_new: &str) -> Result<()> { - self.quota.write().await.take(); + self.quota.write().await.clear(); self.sql .set_raw_config(Config::ConfiguredAddr.as_ref(), Some(primary_new)) diff --git a/src/configure.rs b/src/configure.rs index e72315e7e4..11dc696939 100644 --- a/src/configure.rs +++ b/src/configure.rs @@ -210,7 +210,8 @@ impl Context { /// (i.e. [EnteredLoginParam::addr]). pub async fn delete_transport(&self, addr: &str) -> Result<()> { let now = time(); - self.sql + let removed_transport_id = self + .sql .transaction(|transaction| { let primary_addr = transaction.query_row( "SELECT value FROM config WHERE keyname='configured_addr'", @@ -251,10 +252,11 @@ impl Context { (addr, remove_timestamp), )?; - Ok(()) + Ok(transport_id) }) .await?; send_sync_transports(self).await?; + self.quota.write().await.remove(&removed_transport_id); Ok(()) } diff --git a/src/context.rs b/src/context.rs index afff22e5ab..4f079f9417 100644 --- a/src/context.rs +++ b/src/context.rs @@ -243,9 +243,9 @@ pub struct InnerContext { pub(crate) scheduler: SchedulerState, pub(crate) ratelimit: RwLock, - /// Recently loaded quota information, if any. - /// Set to `None` if quota was never tried to load. - pub(crate) quota: RwLock>, + /// Recently loaded quota information for each trasnport, if any. + /// If quota was never tried to load, then the transport doesn't have an entry in the BTreeMap. + pub(crate) quota: RwLock>, /// Notify about new messages. /// @@ -479,7 +479,7 @@ impl Context { events, scheduler: SchedulerState::new(), ratelimit: RwLock::new(Ratelimit::new(Duration::new(3, 0), 3.0)), // Allow at least 1 message every second + a burst of 3. - quota: RwLock::new(None), + quota: RwLock::new(BTreeMap::new()), new_msgs_notify, server_id: RwLock::new(None), metadata: RwLock::new(None), @@ -614,8 +614,13 @@ impl Context { } // Update quota (to send warning if full) - but only check it once in a while. + // note: For now this only checks quota of primary transport, + // because background check only checks primary transport at the moment if self - .quota_needs_update(DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT) + .quota_needs_update( + session.transport_id(), + DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT, + ) .await && let Err(err) = self.update_recent_quota(&mut session).await { diff --git a/src/quota.rs b/src/quota.rs index d6ba14f79a..f8c70d71da 100644 --- a/src/quota.rs +++ b/src/quota.rs @@ -107,10 +107,10 @@ pub fn needs_quota_warning(curr_percentage: u64, warned_at_percentage: u64) -> b impl Context { /// Returns whether the quota value needs an update. If so, `update_recent_quota()` should be /// called. - pub(crate) async fn quota_needs_update(&self, ratelimit_secs: u64) -> bool { + pub(crate) async fn quota_needs_update(&self, transport_id: u32, ratelimit_secs: u64) -> bool { let quota = self.quota.read().await; quota - .as_ref() + .get(&transport_id) .filter(|quota| time_elapsed("a.modified) < Duration::from_secs(ratelimit_secs)) .is_none() } @@ -155,10 +155,13 @@ impl Context { } } - *self.quota.write().await = Some(QuotaInfo { - recent: quota, - modified: tools::Time::now(), - }); + self.quota.write().await.insert( + session.transport_id(), + QuotaInfo { + recent: quota, + modified: tools::Time::now(), + }, + ); self.emit_event(EventType::ConnectivityChanged); Ok(()) @@ -203,27 +206,42 @@ mod tests { let mut tcm = TestContextManager::new(); let t = &tcm.unconfigured().await; const TIMEOUT: u64 = 60; - assert!(t.quota_needs_update(TIMEOUT).await); - - *t.quota.write().await = Some(QuotaInfo { - recent: Ok(Default::default()), - modified: tools::Time::now() - Duration::from_secs(TIMEOUT + 1), - }); - assert!(t.quota_needs_update(TIMEOUT).await); - - *t.quota.write().await = Some(QuotaInfo { - recent: Ok(Default::default()), - modified: tools::Time::now(), - }); - assert!(!t.quota_needs_update(TIMEOUT).await); + assert!(t.quota_needs_update(0, TIMEOUT).await); + + *t.quota.write().await = { + let mut map = BTreeMap::new(); + map.insert( + 0, + QuotaInfo { + recent: Ok(Default::default()), + modified: tools::Time::now() - Duration::from_secs(TIMEOUT + 1), + }, + ); + map + }; + assert!(t.quota_needs_update(0, TIMEOUT).await); + + *t.quota.write().await = { + let mut map = BTreeMap::new(); + map.insert( + 0, + QuotaInfo { + recent: Ok(Default::default()), + modified: tools::Time::now(), + }, + ); + map + }; + assert!(!t.quota_needs_update(0, TIMEOUT).await); t.evtracker.clear_events(); t.set_primary_self_addr("new@addr").await?; - assert!(t.quota.read().await.is_none()); + assert!(t.quota.read().await.is_empty()); t.evtracker .get_matching(|evt| matches!(evt, EventType::ConnectivityChanged)) .await; - assert!(t.quota_needs_update(TIMEOUT).await); + assert!(t.quota_needs_update(0, TIMEOUT).await); + Ok(()) } } diff --git a/src/scheduler.rs b/src/scheduler.rs index 7dbd625761..538cc1aecf 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -481,7 +481,7 @@ async fn inbox_fetch_idle(ctx: &Context, imap: &mut Imap, mut session: Session) } // Update quota no more than once a minute. - if ctx.quota_needs_update(60).await + if ctx.quota_needs_update(session.transport_id(), 60).await && let Err(err) = ctx.update_recent_quota(&mut session).await { warn!(ctx, "Failed to update quota: {:#}.", err); diff --git a/src/scheduler/connectivity.rs b/src/scheduler/connectivity.rs index 36b3641072..c131242ff1 100644 --- a/src/scheduler/connectivity.rs +++ b/src/scheduler/connectivity.rs @@ -462,21 +462,41 @@ impl Context { // [======67%===== ] // ============================================================================================= - let domain = - &deltachat_contact_tools::EmailAddress::new(&self.get_primary_self_addr().await?)? - .domain; - let storage_on_domain = - escaper::encode_minimal(&stock_str::storage_on_domain(self, domain).await); - ret += &format!("

{storage_on_domain}

    "); + ret += "

    Message Buffers

    "; + let transports = self + .sql + .query_map_vec("SELECT id, addr FROM transports", (), |row| { + let transport_id: u32 = row.get(0)?; + let addr: String = row.get(1)?; + Ok((transport_id, addr)) + }) + .await?; let quota = self.quota.read().await; - if let Some(quota) = &*quota { + ret += "
      "; + for (transport_id, transport_addr) in transports { + let domain = &deltachat_contact_tools::EmailAddress::new(&transport_addr) + .map_or(transport_addr, |email| email.domain); + let domain_escaped = escaper::encode_minimal(domain); + let Some(quota) = quota.get(&transport_id) else { + let not_connected = stock_str::not_connected(self).await; + ret += &format!("
    • {domain_escaped} · {not_connected}
    • "); + continue; + }; match "a.recent { + Err(e) => { + let error_escaped = escaper::encode_minimal(&e.to_string()); + ret += &format!("
    • {domain_escaped} · {error_escaped}
    • "); + } Ok(quota) => { - if !quota.is_empty() { + if quota.is_empty() { + ret += &format!( + "
    • {domain_escaped} · Warning: {domain_escaped} claims to support quota but gives no information
    • " + ); + } else { for (root_name, resources) in quota { use async_imap::types::QuotaResourceName::*; for resource in resources { - ret += "
    • "; + ret += &format!("
    • {domain_escaped} · "); // root name is empty eg. for gmail and redundant eg. for riseup. // therefore, use it only if there are really several roots. @@ -539,21 +559,9 @@ impl Context { ret += "
    • "; } } - } else { - let domain_escaped = escaper::encode_minimal(domain); - ret += &format!( - "
    • Warning: {domain_escaped} claims to support quota but gives no information
    • " - ); } } - Err(e) => { - let error_escaped = escaper::encode_minimal(&e.to_string()); - ret += &format!("
    • {error_escaped}
    • "); - } } - } else { - let not_connected = stock_str::not_connected(self).await; - ret += &format!("
    • {not_connected}
    • "); } ret += "
    "; diff --git a/src/stock_str.rs b/src/stock_str.rs index 291141f0d9..6b0d0ee969 100644 --- a/src/stock_str.rs +++ b/src/stock_str.rs @@ -1144,14 +1144,6 @@ pub(crate) async fn outgoing_messages(context: &Context) -> String { translated(context, StockMessage::OutgoingMessages).await } -/// Stock string: `Storage on %1$s`. -/// `%1$s` will be replaced by the domain of the configured email-address. -pub(crate) async fn storage_on_domain(context: &Context, domain: &str) -> String { - translated(context, StockMessage::StorageOnDomain) - .await - .replace1(domain) -} - /// Stock string: `Not connected`. pub(crate) async fn not_connected(context: &Context) -> String { translated(context, StockMessage::NotConnected).await