mirror of
https://github.com/chatmail/core.git
synced 2026-05-22 16:26:31 +03:00
feat: adapt quota warning to automatic cleanup
This commit is contained in:
@@ -541,9 +541,6 @@ int dc_set_config (dc_context_t* context, const char*
|
|||||||
* an error (no warning as it should be shown to the user) is logged but the attachment is sent anyway.
|
* an error (no warning as it should be shown to the user) is logged but the attachment is sent anyway.
|
||||||
* - `sys.config_keys` = get a space-separated list of all config-keys available.
|
* - `sys.config_keys` = get a space-separated list of all config-keys available.
|
||||||
* The config-keys are the keys that can be passed to the parameter `key` of this function.
|
* The config-keys are the keys that can be passed to the parameter `key` of this function.
|
||||||
* - `quota_exceeding` = 0: quota is unknown or in normal range;
|
|
||||||
* >=80: quota is about to exceed, the value is the concrete percentage,
|
|
||||||
* a device message is added when that happens, however, that value may still be interesting for bots.
|
|
||||||
*
|
*
|
||||||
* @memberof dc_context_t
|
* @memberof dc_context_t
|
||||||
* @param context The context object. For querying system values, this can be NULL.
|
* @param context The context object. For querying system values, this can be NULL.
|
||||||
@@ -7026,11 +7023,7 @@ void dc_event_unref(dc_event_t* event);
|
|||||||
/// Used in message summary text for notifications and chatlist.
|
/// Used in message summary text for notifications and chatlist.
|
||||||
#define DC_STR_FORWARDED 97
|
#define DC_STR_FORWARDED 97
|
||||||
|
|
||||||
/// "Quota exceeding, already %1$s%% used."
|
/// @deprecated 2026-04-25
|
||||||
///
|
|
||||||
/// Used as device message text.
|
|
||||||
///
|
|
||||||
/// `%1$s` will be replaced by the percentage used
|
|
||||||
#define DC_STR_QUOTA_EXCEEDING_MSG_BODY 98
|
#define DC_STR_QUOTA_EXCEEDING_MSG_BODY 98
|
||||||
|
|
||||||
/// "Multi Device Synchronization"
|
/// "Multi Device Synchronization"
|
||||||
|
|||||||
@@ -4961,8 +4961,6 @@ pub async fn was_device_msg_ever_added(context: &Context, label: &str) -> Result
|
|||||||
// no wrong information are shown in the device chat
|
// no wrong information are shown in the device chat
|
||||||
// - deletion in `devmsglabels` makes sure,
|
// - deletion in `devmsglabels` makes sure,
|
||||||
// deleted messages are reset and useful messages can be added again
|
// deleted messages are reset and useful messages can be added again
|
||||||
// - we reset the config-option `QuotaExceeding`
|
|
||||||
// that is used as a helper to drive the corresponding device message.
|
|
||||||
pub(crate) async fn delete_and_reset_all_device_msgs(context: &Context) -> Result<()> {
|
pub(crate) async fn delete_and_reset_all_device_msgs(context: &Context) -> Result<()> {
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
@@ -4978,9 +4976,6 @@ pub(crate) async fn delete_and_reset_all_device_msgs(context: &Context) -> Resul
|
|||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
context
|
|
||||||
.set_config_internal(Config::QuotaExceeding, None)
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -371,11 +371,6 @@ pub enum Config {
|
|||||||
#[strum(props(default = "0"))]
|
#[strum(props(default = "0"))]
|
||||||
NotifyAboutWrongPw,
|
NotifyAboutWrongPw,
|
||||||
|
|
||||||
/// If a warning about exceeding quota was shown recently,
|
|
||||||
/// this is the percentage of quota at the time the warning was given.
|
|
||||||
/// Unset, when quota falls below minimal warning threshold again.
|
|
||||||
QuotaExceeding,
|
|
||||||
|
|
||||||
/// Timestamp of the last time housekeeping was run
|
/// Timestamp of the last time housekeeping was run
|
||||||
LastHousekeeping,
|
LastHousekeeping,
|
||||||
|
|
||||||
|
|||||||
@@ -1004,12 +1004,6 @@ impl Context {
|
|||||||
.await?
|
.await?
|
||||||
.to_string(),
|
.to_string(),
|
||||||
);
|
);
|
||||||
res.insert(
|
|
||||||
"quota_exceeding",
|
|
||||||
self.get_config_int(Config::QuotaExceeding)
|
|
||||||
.await?
|
|
||||||
.to_string(),
|
|
||||||
);
|
|
||||||
res.insert(
|
res.insert(
|
||||||
"authserv_id_candidates",
|
"authserv_id_candidates",
|
||||||
self.get_config(Config::AuthservIdCandidates)
|
self.get_config(Config::AuthservIdCandidates)
|
||||||
|
|||||||
98
src/quota.rs
98
src/quota.rs
@@ -6,33 +6,17 @@ use std::time::Duration;
|
|||||||
use anyhow::{Context as _, Result, anyhow};
|
use anyhow::{Context as _, Result, anyhow};
|
||||||
use async_imap::types::{Quota, QuotaResource};
|
use async_imap::types::{Quota, QuotaResource};
|
||||||
|
|
||||||
use crate::chat::add_device_msg_with_importance;
|
|
||||||
use crate::config::Config;
|
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::imap::session::Session as ImapSession;
|
use crate::imap::session::Session as ImapSession;
|
||||||
use crate::log::warn;
|
|
||||||
use crate::message::Message;
|
|
||||||
use crate::tools::{self, time_elapsed};
|
use crate::tools::{self, time_elapsed};
|
||||||
use crate::{EventType, stock_str};
|
use crate::{EventType, stock_str};
|
||||||
|
|
||||||
/// warn about a nearly full mailbox after this usage percentage is reached.
|
/// quota icon in connectivity is "yellow".
|
||||||
/// quota icon is "yellow".
|
|
||||||
pub const QUOTA_WARN_THRESHOLD_PERCENTAGE: u64 = 80;
|
pub const QUOTA_WARN_THRESHOLD_PERCENTAGE: u64 = 80;
|
||||||
|
|
||||||
/// warning again after this usage percentage is reached,
|
/// quota icon in connectivity is "red".
|
||||||
/// quota icon is "red".
|
|
||||||
pub const QUOTA_ERROR_THRESHOLD_PERCENTAGE: u64 = 95;
|
pub const QUOTA_ERROR_THRESHOLD_PERCENTAGE: u64 = 95;
|
||||||
|
|
||||||
/// if quota is below this value (again),
|
|
||||||
/// QuotaExceeding is cleared.
|
|
||||||
///
|
|
||||||
/// This value should be a bit below QUOTA_WARN_THRESHOLD_PERCENTAGE to
|
|
||||||
/// avoid jittering and lots of warnings when quota is exactly at the warning threshold.
|
|
||||||
///
|
|
||||||
/// We do not repeat warnings on a daily base or so as some provider
|
|
||||||
/// providers report bad values and we would then spam the user.
|
|
||||||
pub const QUOTA_ALLCLEAR_PERCENTAGE: u64 = 75;
|
|
||||||
|
|
||||||
/// Server quota information with an update timestamp.
|
/// Server quota information with an update timestamp.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct QuotaInfo {
|
pub struct QuotaInfo {
|
||||||
@@ -70,37 +54,6 @@ async fn get_unique_quota_roots_and_usage(
|
|||||||
Ok(unique_quota_roots)
|
Ok(unique_quota_roots)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_highest_usage<'t>(
|
|
||||||
unique_quota_roots: &'t BTreeMap<String, Vec<QuotaResource>>,
|
|
||||||
) -> Result<(u64, &'t String, &'t QuotaResource)> {
|
|
||||||
let mut highest: Option<(u64, &'t String, &QuotaResource)> = None;
|
|
||||||
for (name, resources) in unique_quota_roots {
|
|
||||||
for r in resources {
|
|
||||||
let usage_percent = r.get_usage_percentage();
|
|
||||||
match highest {
|
|
||||||
None => {
|
|
||||||
highest = Some((usage_percent, name, r));
|
|
||||||
}
|
|
||||||
Some((up, ..)) => {
|
|
||||||
if up <= usage_percent {
|
|
||||||
highest = Some((usage_percent, name, r));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
highest.context("no quota_resource found, this is unexpected")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks if a quota warning is needed.
|
|
||||||
pub fn needs_quota_warning(curr_percentage: u64, warned_at_percentage: u64) -> bool {
|
|
||||||
(curr_percentage >= QUOTA_WARN_THRESHOLD_PERCENTAGE
|
|
||||||
&& warned_at_percentage < QUOTA_WARN_THRESHOLD_PERCENTAGE)
|
|
||||||
|| (curr_percentage >= QUOTA_ERROR_THRESHOLD_PERCENTAGE
|
|
||||||
&& warned_at_percentage < QUOTA_ERROR_THRESHOLD_PERCENTAGE)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
/// Returns whether the quota value needs an update. If so, `update_recent_quota()` should be
|
/// Returns whether the quota value needs an update. If so, `update_recent_quota()` should be
|
||||||
/// called.
|
/// called.
|
||||||
@@ -134,32 +87,6 @@ impl Context {
|
|||||||
Err(anyhow!(stock_str::not_supported_by_provider(self)))
|
Err(anyhow!(stock_str::not_supported_by_provider(self)))
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Ok(quota) = "a {
|
|
||||||
match get_highest_usage(quota) {
|
|
||||||
Ok((highest, _, _)) => {
|
|
||||||
if needs_quota_warning(
|
|
||||||
highest,
|
|
||||||
self.get_config_int(Config::QuotaExceeding).await? as u64,
|
|
||||||
) {
|
|
||||||
self.set_config_internal(
|
|
||||||
Config::QuotaExceeding,
|
|
||||||
Some(&highest.to_string()),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
let mut msg = Message::new_text(stock_str::quota_exceeding(self, highest));
|
|
||||||
add_device_msg_with_importance(self, None, Some(&mut msg), true).await?;
|
|
||||||
} else if highest <= QUOTA_ALLCLEAR_PERCENTAGE {
|
|
||||||
self.set_config_internal(Config::QuotaExceeding, None)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => warn!(
|
|
||||||
self,
|
|
||||||
"Transport {transport_id}: Cannot get highest quota usage: {err:#}"
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.quota.write().await.insert(
|
self.quota.write().await.insert(
|
||||||
transport_id,
|
transport_id,
|
||||||
QuotaInfo {
|
QuotaInfo {
|
||||||
@@ -179,29 +106,10 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::test_utils::TestContextManager;
|
use crate::test_utils::TestContextManager;
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
||||||
async fn test_needs_quota_warning() -> Result<()> {
|
|
||||||
assert!(!needs_quota_warning(0, 0));
|
|
||||||
assert!(!needs_quota_warning(10, 0));
|
|
||||||
assert!(!needs_quota_warning(70, 0));
|
|
||||||
assert!(!needs_quota_warning(75, 0));
|
|
||||||
assert!(!needs_quota_warning(79, 0));
|
|
||||||
assert!(needs_quota_warning(80, 0));
|
|
||||||
assert!(needs_quota_warning(81, 0));
|
|
||||||
assert!(!needs_quota_warning(85, 80));
|
|
||||||
assert!(!needs_quota_warning(85, 81));
|
|
||||||
assert!(needs_quota_warning(95, 82));
|
|
||||||
assert!(!needs_quota_warning(97, 95));
|
|
||||||
assert!(!needs_quota_warning(97, 96));
|
|
||||||
assert!(!needs_quota_warning(1000, 96));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[expect(clippy::assertions_on_constants)]
|
#[expect(clippy::assertions_on_constants)]
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
async fn test_quota_thresholds() -> anyhow::Result<()> {
|
async fn test_quota_thresholds() -> anyhow::Result<()> {
|
||||||
assert!(QUOTA_ALLCLEAR_PERCENTAGE > 50);
|
assert!(0 < QUOTA_WARN_THRESHOLD_PERCENTAGE);
|
||||||
assert!(QUOTA_ALLCLEAR_PERCENTAGE < QUOTA_WARN_THRESHOLD_PERCENTAGE);
|
|
||||||
assert!(QUOTA_WARN_THRESHOLD_PERCENTAGE < QUOTA_ERROR_THRESHOLD_PERCENTAGE);
|
assert!(QUOTA_WARN_THRESHOLD_PERCENTAGE < QUOTA_ERROR_THRESHOLD_PERCENTAGE);
|
||||||
assert!(QUOTA_ERROR_THRESHOLD_PERCENTAGE < 100);
|
assert!(QUOTA_ERROR_THRESHOLD_PERCENTAGE < 100);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -153,15 +153,6 @@ pub enum StockMessage {
|
|||||||
#[strum(props(fallback = "Forwarded"))]
|
#[strum(props(fallback = "Forwarded"))]
|
||||||
Forwarded = 97,
|
Forwarded = 97,
|
||||||
|
|
||||||
#[strum(props(
|
|
||||||
fallback = "⚠️ Your provider's storage is about to exceed, already %1$s%% are used.\n\n\
|
|
||||||
You may not be able to receive message when the storage is 100%% used.\n\n\
|
|
||||||
👉 Please check if you can delete old data in the provider's webinterface \
|
|
||||||
and consider to enable \"Settings / Delete Old Messages\". \
|
|
||||||
You can check your current storage usage anytime at \"Settings / Connectivity\"."
|
|
||||||
))]
|
|
||||||
QuotaExceedingMsgBody = 98,
|
|
||||||
|
|
||||||
#[strum(props(fallback = "Multi Device Synchronization"))]
|
#[strum(props(fallback = "Multi Device Synchronization"))]
|
||||||
SyncMsgSubject = 101,
|
SyncMsgSubject = 101,
|
||||||
|
|
||||||
@@ -1100,13 +1091,6 @@ pub(crate) fn forwarded(context: &Context) -> String {
|
|||||||
translated(context, StockMessage::Forwarded)
|
translated(context, StockMessage::Forwarded)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stock string: `⚠️ Your provider's storage is about to exceed...`.
|
|
||||||
pub(crate) fn quota_exceeding(context: &Context, highest_usage: u64) -> String {
|
|
||||||
translated(context, StockMessage::QuotaExceedingMsgBody)
|
|
||||||
.replace1(&format!("{highest_usage}"))
|
|
||||||
.replace("%%", "%")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stock string: `Incoming Messages`.
|
/// Stock string: `Incoming Messages`.
|
||||||
pub(crate) fn incoming_messages(context: &Context) -> String {
|
pub(crate) fn incoming_messages(context: &Context) -> String {
|
||||||
translated(context, StockMessage::IncomingMessages)
|
translated(context, StockMessage::IncomingMessages)
|
||||||
|
|||||||
@@ -102,16 +102,6 @@ async fn test_stock_system_msg_add_member_by_other_with_displayname() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
||||||
async fn test_quota_exceeding_stock_str() -> Result<()> {
|
|
||||||
let t = TestContext::new().await;
|
|
||||||
let str = quota_exceeding(&t, 81);
|
|
||||||
assert!(str.contains("81% "));
|
|
||||||
assert!(str.contains("100% "));
|
|
||||||
assert!(!str.contains("%%"));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
async fn test_update_device_chats() {
|
async fn test_update_device_chats() {
|
||||||
let t = TestContext::new_alice().await;
|
let t = TestContext::new_alice().await;
|
||||||
|
|||||||
Reference in New Issue
Block a user