Skip to content

Commit 9ac64ea

Browse files
authored
feat: connectivity view: quota for all transports (#7630)
- **show quota of all relays** - **remove `DC_STR_STORAGE_ON_DOMAIN` stock string** - renames the quota section to "Relay Capacity" until we come up with a better name in #7580 (comment) closes #7591 <img width="300" alt="image" src="https://github.com/user-attachments/assets/1909dccd-e6b3-42e6-963f-004b2b464db7" /> <img width="300" alt="image" src="https://github.com/user-attachments/assets/1e97e67b-e0ed-492b-95a0-6ef12595abe4" />
1 parent 294e23d commit 9ac64ea

File tree

8 files changed

+84
-65
lines changed

8 files changed

+84
-65
lines changed

deltachat-ffi/deltachat.h

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7314,12 +7314,6 @@ void dc_event_unref(dc_event_t* event);
73147314
/// Used as a headline in the connectivity view.
73157315
#define DC_STR_OUTGOING_MESSAGES 104
73167316

7317-
/// "Storage on %1$s"
7318-
///
7319-
/// Used as a headline in the connectivity view.
7320-
///
7321-
/// `%1$s` will be replaced by the domain of the configured e-mail address.
7322-
#define DC_STR_STORAGE_ON_DOMAIN 105
73237317

73247318
/// @deprecated Deprecated 2022-04-16, this string is no longer needed.
73257319
#define DC_STR_ONE_MOMENT 106

src/config.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -944,7 +944,7 @@ impl Context {
944944
/// This should only be used by test code and during configure.
945945
#[cfg(test)] // AEAP is disabled, but there are still tests for it
946946
pub(crate) async fn set_primary_self_addr(&self, primary_new: &str) -> Result<()> {
947-
self.quota.write().await.take();
947+
self.quota.write().await.clear();
948948

949949
self.sql
950950
.set_raw_config(Config::ConfiguredAddr.as_ref(), Some(primary_new))

src/configure.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,8 @@ impl Context {
210210
/// (i.e. [EnteredLoginParam::addr]).
211211
pub async fn delete_transport(&self, addr: &str) -> Result<()> {
212212
let now = time();
213-
self.sql
213+
let removed_transport_id = self
214+
.sql
214215
.transaction(|transaction| {
215216
let primary_addr = transaction.query_row(
216217
"SELECT value FROM config WHERE keyname='configured_addr'",
@@ -251,10 +252,11 @@ impl Context {
251252
(addr, remove_timestamp),
252253
)?;
253254

254-
Ok(())
255+
Ok(transport_id)
255256
})
256257
.await?;
257258
send_sync_transports(self).await?;
259+
self.quota.write().await.remove(&removed_transport_id);
258260

259261
Ok(())
260262
}

src/context.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -243,9 +243,9 @@ pub struct InnerContext {
243243
pub(crate) scheduler: SchedulerState,
244244
pub(crate) ratelimit: RwLock<Ratelimit>,
245245

246-
/// Recently loaded quota information, if any.
247-
/// Set to `None` if quota was never tried to load.
248-
pub(crate) quota: RwLock<Option<QuotaInfo>>,
246+
/// Recently loaded quota information for each trasnport, if any.
247+
/// If quota was never tried to load, then the transport doesn't have an entry in the BTreeMap.
248+
pub(crate) quota: RwLock<BTreeMap<u32, QuotaInfo>>,
249249

250250
/// Notify about new messages.
251251
///
@@ -479,7 +479,7 @@ impl Context {
479479
events,
480480
scheduler: SchedulerState::new(),
481481
ratelimit: RwLock::new(Ratelimit::new(Duration::new(3, 0), 3.0)), // Allow at least 1 message every second + a burst of 3.
482-
quota: RwLock::new(None),
482+
quota: RwLock::new(BTreeMap::new()),
483483
new_msgs_notify,
484484
server_id: RwLock::new(None),
485485
metadata: RwLock::new(None),
@@ -614,8 +614,13 @@ impl Context {
614614
}
615615

616616
// Update quota (to send warning if full) - but only check it once in a while.
617+
// note: For now this only checks quota of primary transport,
618+
// because background check only checks primary transport at the moment
617619
if self
618-
.quota_needs_update(DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT)
620+
.quota_needs_update(
621+
session.transport_id(),
622+
DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT,
623+
)
619624
.await
620625
&& let Err(err) = self.update_recent_quota(&mut session).await
621626
{

src/quota.rs

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,10 @@ pub fn needs_quota_warning(curr_percentage: u64, warned_at_percentage: u64) -> b
107107
impl Context {
108108
/// Returns whether the quota value needs an update. If so, `update_recent_quota()` should be
109109
/// called.
110-
pub(crate) async fn quota_needs_update(&self, ratelimit_secs: u64) -> bool {
110+
pub(crate) async fn quota_needs_update(&self, transport_id: u32, ratelimit_secs: u64) -> bool {
111111
let quota = self.quota.read().await;
112112
quota
113-
.as_ref()
113+
.get(&transport_id)
114114
.filter(|quota| time_elapsed(&quota.modified) < Duration::from_secs(ratelimit_secs))
115115
.is_none()
116116
}
@@ -155,10 +155,13 @@ impl Context {
155155
}
156156
}
157157

158-
*self.quota.write().await = Some(QuotaInfo {
159-
recent: quota,
160-
modified: tools::Time::now(),
161-
});
158+
self.quota.write().await.insert(
159+
session.transport_id(),
160+
QuotaInfo {
161+
recent: quota,
162+
modified: tools::Time::now(),
163+
},
164+
);
162165

163166
self.emit_event(EventType::ConnectivityChanged);
164167
Ok(())
@@ -203,27 +206,42 @@ mod tests {
203206
let mut tcm = TestContextManager::new();
204207
let t = &tcm.unconfigured().await;
205208
const TIMEOUT: u64 = 60;
206-
assert!(t.quota_needs_update(TIMEOUT).await);
207-
208-
*t.quota.write().await = Some(QuotaInfo {
209-
recent: Ok(Default::default()),
210-
modified: tools::Time::now() - Duration::from_secs(TIMEOUT + 1),
211-
});
212-
assert!(t.quota_needs_update(TIMEOUT).await);
213-
214-
*t.quota.write().await = Some(QuotaInfo {
215-
recent: Ok(Default::default()),
216-
modified: tools::Time::now(),
217-
});
218-
assert!(!t.quota_needs_update(TIMEOUT).await);
209+
assert!(t.quota_needs_update(0, TIMEOUT).await);
210+
211+
*t.quota.write().await = {
212+
let mut map = BTreeMap::new();
213+
map.insert(
214+
0,
215+
QuotaInfo {
216+
recent: Ok(Default::default()),
217+
modified: tools::Time::now() - Duration::from_secs(TIMEOUT + 1),
218+
},
219+
);
220+
map
221+
};
222+
assert!(t.quota_needs_update(0, TIMEOUT).await);
223+
224+
*t.quota.write().await = {
225+
let mut map = BTreeMap::new();
226+
map.insert(
227+
0,
228+
QuotaInfo {
229+
recent: Ok(Default::default()),
230+
modified: tools::Time::now(),
231+
},
232+
);
233+
map
234+
};
235+
assert!(!t.quota_needs_update(0, TIMEOUT).await);
219236

220237
t.evtracker.clear_events();
221238
t.set_primary_self_addr("new@addr").await?;
222-
assert!(t.quota.read().await.is_none());
239+
assert!(t.quota.read().await.is_empty());
223240
t.evtracker
224241
.get_matching(|evt| matches!(evt, EventType::ConnectivityChanged))
225242
.await;
226-
assert!(t.quota_needs_update(TIMEOUT).await);
243+
assert!(t.quota_needs_update(0, TIMEOUT).await);
244+
227245
Ok(())
228246
}
229247
}

src/scheduler.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,7 @@ async fn inbox_fetch_idle(ctx: &Context, imap: &mut Imap, mut session: Session)
481481
}
482482

483483
// Update quota no more than once a minute.
484-
if ctx.quota_needs_update(60).await
484+
if ctx.quota_needs_update(session.transport_id(), 60).await
485485
&& let Err(err) = ctx.update_recent_quota(&mut session).await
486486
{
487487
warn!(ctx, "Failed to update quota: {:#}.", err);

src/scheduler/connectivity.rs

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -462,21 +462,41 @@ impl Context {
462462
// [======67%===== ]
463463
// =============================================================================================
464464

465-
let domain =
466-
&deltachat_contact_tools::EmailAddress::new(&self.get_primary_self_addr().await?)?
467-
.domain;
468-
let storage_on_domain =
469-
escaper::encode_minimal(&stock_str::storage_on_domain(self, domain).await);
470-
ret += &format!("<h3>{storage_on_domain}</h3><ul>");
465+
ret += "<h3>Message Buffers</h3>";
466+
let transports = self
467+
.sql
468+
.query_map_vec("SELECT id, addr FROM transports", (), |row| {
469+
let transport_id: u32 = row.get(0)?;
470+
let addr: String = row.get(1)?;
471+
Ok((transport_id, addr))
472+
})
473+
.await?;
471474
let quota = self.quota.read().await;
472-
if let Some(quota) = &*quota {
475+
ret += "<ul>";
476+
for (transport_id, transport_addr) in transports {
477+
let domain = &deltachat_contact_tools::EmailAddress::new(&transport_addr)
478+
.map_or(transport_addr, |email| email.domain);
479+
let domain_escaped = escaper::encode_minimal(domain);
480+
let Some(quota) = quota.get(&transport_id) else {
481+
let not_connected = stock_str::not_connected(self).await;
482+
ret += &format!("<li>{domain_escaped} &middot; {not_connected}</li>");
483+
continue;
484+
};
473485
match &quota.recent {
486+
Err(e) => {
487+
let error_escaped = escaper::encode_minimal(&e.to_string());
488+
ret += &format!("<li>{domain_escaped} &middot; {error_escaped}</li>");
489+
}
474490
Ok(quota) => {
475-
if !quota.is_empty() {
491+
if quota.is_empty() {
492+
ret += &format!(
493+
"<li>{domain_escaped} &middot; Warning: {domain_escaped} claims to support quota but gives no information</li>"
494+
);
495+
} else {
476496
for (root_name, resources) in quota {
477497
use async_imap::types::QuotaResourceName::*;
478498
for resource in resources {
479-
ret += "<li>";
499+
ret += &format!("<li>{domain_escaped} &middot; ");
480500

481501
// root name is empty eg. for gmail and redundant eg. for riseup.
482502
// therefore, use it only if there are really several roots.
@@ -539,21 +559,9 @@ impl Context {
539559
ret += "</li>";
540560
}
541561
}
542-
} else {
543-
let domain_escaped = escaper::encode_minimal(domain);
544-
ret += &format!(
545-
"<li>Warning: {domain_escaped} claims to support quota but gives no information</li>"
546-
);
547562
}
548563
}
549-
Err(e) => {
550-
let error_escaped = escaper::encode_minimal(&e.to_string());
551-
ret += &format!("<li>{error_escaped}</li>");
552-
}
553564
}
554-
} else {
555-
let not_connected = stock_str::not_connected(self).await;
556-
ret += &format!("<li>{not_connected}</li>");
557565
}
558566
ret += "</ul>";
559567

src/stock_str.rs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,14 +1144,6 @@ pub(crate) async fn outgoing_messages(context: &Context) -> String {
11441144
translated(context, StockMessage::OutgoingMessages).await
11451145
}
11461146

1147-
/// Stock string: `Storage on %1$s`.
1148-
/// `%1$s` will be replaced by the domain of the configured email-address.
1149-
pub(crate) async fn storage_on_domain(context: &Context, domain: &str) -> String {
1150-
translated(context, StockMessage::StorageOnDomain)
1151-
.await
1152-
.replace1(domain)
1153-
}
1154-
11551147
/// Stock string: `Not connected`.
11561148
pub(crate) async fn not_connected(context: &Context) -> String {
11571149
translated(context, StockMessage::NotConnected).await

0 commit comments

Comments
 (0)