Compare commits

...

12 Commits

Author SHA1 Message Date
link2xt
8ac9c6bb09 more debug logging 2026-03-25 23:03:59 +01:00
link2xt
84459b6495 WIP: more delay debugging 2026-03-25 22:32:12 +01:00
link2xt
c99b8a4482 feat: improve IMAP loop logs
Only inbox loop is changed because non-inbox loop is going to be removed
together with `mvbox_move`.

Added transport IDs to the log and logging around quota updates.
Removed some logs that add noise,
like logging that IDLE is supported each time right before using it.
2026-03-25 20:31:53 +00:00
link2xt
76e2c36d85 refactor: cleanup remaining Autocrypt Setup Message processing in mimeparser 2026-03-25 19:54:19 +00:00
link2xt
1b8bf4ed23 api: add JSON-RPC API markfresh_chat() 2026-03-25 19:53:44 +00:00
link2xt
c553357c60 docs: move changelog entry for dc_markfresh_chat to API changes 2026-03-25 19:53:44 +00:00
link2xt
ebe8550c52 chore: fix clippy warnings 2026-03-25 19:53:10 +00:00
link2xt
2637c3bea4 refactor: replace async RwLock with sync RwLock for stock strings 2026-03-25 19:48:40 +00:00
iequidoo
d1f1633c60 refactor: Remove wal_checkpoint_mutex, lock write_mutex before getting sql connection instead
The original idea was to always lock `write_mutex` before acquiring an `InnerPool.semaphore` permit
to avoid ABBA deadlocks, but when refactoring a PR for b696a242fc,
that was forgotten.

This doesn't really change the program flow as we have `Context::housekeeping_mutex` anyway,
just simplifies the code.
2026-03-25 06:13:10 -03:00
iequidoo
98b55ec15f refactor(ffi): Correctly declare dc_event_channel_new() as having no params (#7831)
In C, `foo()` means that the function accepts an unspecified number of arguments and this is
deprecated.
2026-03-24 16:22:35 -03:00
link2xt
6a3ef20a99 chore(cargo): update rustls-webpki to 0.103.10
Upgrading fixes RUSTSEC-2026-0049 for our usage
of TLS for SMTP and IMAP.

This introduces duplicate dependency because iroh
still depends 0.102.
2026-03-24 12:09:20 +00:00
link2xt
59be03a7eb chore: bump version to 2.48.0-dev 2026-03-24 04:30:06 +01:00
57 changed files with 551 additions and 560 deletions

View File

@@ -39,11 +39,11 @@
- [**breaking**] remove functions for sending and receiving Autocrypt Setup Message.
- Add `list_transports_ex()` and `set_transport_unpublished()` functions.
- Add API `dc_markfresh_chat` to mark messages as "fresh".
### Features / Changes
- add `IncomingCallAccepted.from_this_device`.
- mark messages as "fresh".
- decode `dcaccount://` URLs and error out on empty URLs early.
- enable anonymous OpenPGP key IDs.
- tls: do not verify TLS certificates for hostnames starting with `_`.

35
Cargo.lock generated
View File

@@ -1307,7 +1307,7 @@ dependencies = [
[[package]]
name = "deltachat"
version = "2.47.0"
version = "2.48.0-dev"
dependencies = [
"anyhow",
"astral-tokio-tar",
@@ -1416,7 +1416,7 @@ dependencies = [
[[package]]
name = "deltachat-jsonrpc"
version = "2.47.0"
version = "2.48.0-dev"
dependencies = [
"anyhow",
"async-channel 2.5.0",
@@ -1437,7 +1437,7 @@ dependencies = [
[[package]]
name = "deltachat-repl"
version = "2.47.0"
version = "2.48.0-dev"
dependencies = [
"anyhow",
"deltachat",
@@ -1453,7 +1453,7 @@ dependencies = [
[[package]]
name = "deltachat-rpc-server"
version = "2.47.0"
version = "2.48.0-dev"
dependencies = [
"anyhow",
"deltachat",
@@ -1482,7 +1482,7 @@ dependencies = [
[[package]]
name = "deltachat_ffi"
version = "2.47.0"
version = "2.48.0-dev"
dependencies = [
"anyhow",
"deltachat",
@@ -2986,7 +2986,7 @@ dependencies = [
"reqwest",
"ring",
"rustls",
"rustls-webpki",
"rustls-webpki 0.102.8",
"serde",
"smallvec",
"spki",
@@ -3175,7 +3175,7 @@ dependencies = [
"rand 0.8.5",
"reqwest",
"rustls",
"rustls-webpki",
"rustls-webpki 0.102.8",
"serde",
"sha1",
"strum 0.26.2",
@@ -5156,15 +5156,15 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.23.23"
version = "0.23.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395"
checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4"
dependencies = [
"log",
"once_cell",
"ring",
"rustls-pki-types",
"rustls-webpki",
"rustls-webpki 0.103.10",
"subtle",
"zeroize",
]
@@ -5199,6 +5199,17 @@ dependencies = [
"untrusted",
]
[[package]]
name = "rustls-webpki"
version = "0.103.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]]
name = "rustversion"
version = "1.0.14"
@@ -6171,9 +6182,9 @@ dependencies = [
[[package]]
name = "tokio-rustls"
version = "0.26.2"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b"
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
dependencies = [
"rustls",
"tokio",

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat"
version = "2.47.0"
version = "2.48.0-dev"
edition = "2024"
license = "MPL-2.0"
rust-version = "1.88"

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat_ffi"
version = "2.47.0"
version = "2.48.0-dev"
description = "Deltachat FFI"
edition = "2018"
readme = "README.md"

View File

@@ -5959,7 +5959,7 @@ char* dc_jsonrpc_blocking_call(dc_jsonrpc_instance_t* jsonrpc_instance, const ch
* @memberof dc_event_channel_t
* @return An event channel wrapper object (dc_event_channel_t).
*/
dc_event_channel_t* dc_event_channel_new();
dc_event_channel_t* dc_event_channel_new(void);
/**
* Release/free the events channel structure.

View File

@@ -306,20 +306,17 @@ pub unsafe extern "C" fn dc_set_stock_translation(
let msg = to_string_lossy(stock_msg);
let ctx = &*context;
block_on(async move {
match StockMessage::from_u32(stock_id)
.with_context(|| format!("Invalid stock message ID {stock_id}"))
match StockMessage::from_u32(stock_id)
.with_context(|| format!("Invalid stock message ID {stock_id}"))
.log_err(ctx)
{
Ok(id) => ctx
.set_stock_translation(id, msg)
.context("set_stock_translation failed")
.log_err(ctx)
{
Ok(id) => ctx
.set_stock_translation(id, msg)
.await
.context("set_stock_translation failed")
.log_err(ctx)
.is_ok() as libc::c_int,
Err(_) => 0,
}
})
.is_ok() as libc::c_int,
Err(_) => 0,
}
}
#[no_mangle]

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-jsonrpc"
version = "2.47.0"
version = "2.48.0-dev"
description = "DeltaChat JSON-RPC API"
edition = "2021"
license = "MPL-2.0"

View File

@@ -11,8 +11,8 @@ use deltachat::blob::BlobObject;
use deltachat::calls::ice_servers;
use deltachat::chat::{
self, add_contact_to_chat, forward_msgs, forward_msgs_2ctx, get_chat_media, get_chat_msgs,
get_chat_msgs_ex, marknoticed_all_chats, marknoticed_chat, remove_contact_from_chat, Chat,
ChatId, ChatItem, MessageListOptions,
get_chat_msgs_ex, markfresh_chat, marknoticed_all_chats, marknoticed_chat,
remove_contact_from_chat, Chat, ChatId, ChatItem, MessageListOptions,
};
use deltachat::chatlist::Chatlist;
use deltachat::config::{get_all_ui_config_keys, Config};
@@ -473,9 +473,7 @@ impl CommandApi {
let accounts = self.accounts.read().await;
for (stock_id, stock_message) in strings {
if let Some(stock_id) = StockMessage::from_u32(stock_id) {
accounts
.set_stock_translation(stock_id, stock_message)
.await?;
accounts.set_stock_translation(stock_id, stock_message)?;
}
}
Ok(())
@@ -1251,6 +1249,15 @@ impl CommandApi {
marknoticed_chat(&ctx, ChatId::new(chat_id)).await
}
/// Marks the last incoming message in the chat as _fresh_.
///
/// UI can use this to offer a "mark unread" option,
/// so that already noticed chats get a badge counter again.
async fn markfresh_chat(&self, account_id: u32, chat_id: u32) -> Result<()> {
let ctx = self.get_context(account_id).await?;
markfresh_chat(&ctx, ChatId::new(chat_id)).await
}
/// Returns the message that is immediately followed by the last seen
/// message.
/// From the point of view of the user this is effectively

View File

@@ -54,5 +54,5 @@
},
"type": "module",
"types": "dist/deltachat.d.ts",
"version": "2.47.0"
"version": "2.48.0-dev"
}

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-repl"
version = "2.47.0"
version = "2.48.0-dev"
license = "MPL-2.0"
edition = "2021"
repository = "https://github.com/chatmail/core"

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "deltachat-rpc-client"
version = "2.47.0"
version = "2.48.0-dev"
license = "MPL-2.0"
description = "Python client for Delta Chat core JSON-RPC interface"
classifiers = [

View File

@@ -219,6 +219,10 @@ class Chat:
"""Mark all messages in this chat as noticed."""
self._rpc.marknoticed_chat(self.account.id, self.id)
def mark_fresh(self) -> None:
"""Mark the last incoming message in the chat as fresh."""
self._rpc.markfresh_chat(self.account.id, self.id)
def add_contact(self, *contact: Union[int, str, Contact, "Account"]) -> None:
"""Add contacts to this group."""
from .account import Account

View File

@@ -273,6 +273,9 @@ def test_chat(acfactory) -> None:
assert group.get_messages()
group.get_fresh_message_count()
group.mark_noticed()
assert group.get_fresh_message_count() == 0
group.mark_fresh()
assert group.get_fresh_message_count() > 0
assert group.get_contacts()
assert group.get_past_contacts() == []
group.remove_contact(alice_contact_bob)

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-rpc-server"
version = "2.47.0"
version = "2.48.0-dev"
description = "DeltaChat JSON-RPC server"
edition = "2021"
readme = "README.md"

View File

@@ -15,5 +15,5 @@
},
"type": "module",
"types": "index.d.ts",
"version": "2.47.0"
"version": "2.48.0-dev"
}

View File

@@ -49,6 +49,7 @@ skip = [
{ name = "rand_core", version = "0.6.4" },
{ name = "rand", version = "0.8.5" },
{ name = "rustix", version = "0.38.44" },
{ name = "rustls-webpki", version = "0.102.8" },
{ name = "serdect", version = "0.2.0" },
{ name = "socket2", version = "0.5.9" },
{ name = "spin", version = "0.9.8" },

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "deltachat"
version = "2.47.0"
version = "2.48.0-dev"
license = "MPL-2.0"
description = "Python bindings for the Delta Chat Core library using CFFI against the Rust-implemented libdeltachat"
readme = "README.rst"

View File

@@ -1249,13 +1249,11 @@ mod tests {
let account1 = accounts.get_account(1).context("failed to get account 1")?;
let account2 = accounts.get_account(2).context("failed to get account 2")?;
assert_eq!(stock_str::no_messages(&account1).await, "No messages.");
assert_eq!(stock_str::no_messages(&account2).await, "No messages.");
account1
.set_stock_translation(StockMessage::NoMessages, "foobar".to_string())
.await?;
assert_eq!(stock_str::no_messages(&account1).await, "foobar");
assert_eq!(stock_str::no_messages(&account2).await, "foobar");
assert_eq!(stock_str::no_messages(&account1), "No messages.");
assert_eq!(stock_str::no_messages(&account2), "No messages.");
account1.set_stock_translation(StockMessage::NoMessages, "foobar".to_string())?;
assert_eq!(stock_str::no_messages(&account1), "foobar");
assert_eq!(stock_str::no_messages(&account2), "foobar");
Ok(())
}

View File

@@ -104,13 +104,11 @@ impl CallInfo {
};
if self.is_incoming() {
let incoming_call_str =
stock_str::incoming_call(context, self.has_video_initially()).await;
let incoming_call_str = stock_str::incoming_call(context, self.has_video_initially());
self.update_text(context, &format!("{incoming_call_str}\n{duration}"))
.await?;
} else {
let outgoing_call_str =
stock_str::outgoing_call(context, self.has_video_initially()).await;
let outgoing_call_str = stock_str::outgoing_call(context, self.has_video_initially());
self.update_text(context, &format!("{outgoing_call_str}\n{duration}"))
.await?;
}
@@ -207,7 +205,7 @@ impl Context {
);
ensure!(!chat.is_self_talk(), "Cannot call self");
let outgoing_call_str = stock_str::outgoing_call(self, has_video_initially).await;
let outgoing_call_str = stock_str::outgoing_call(self, has_video_initially);
let mut call = Message {
viewtype: Viewtype::Call,
text: outgoing_call_str,
@@ -286,11 +284,11 @@ impl Context {
if call.is_incoming() {
call.mark_as_ended(self).await?;
markseen_msgs(self, vec![call_id]).await?;
let declined_call_str = stock_str::declined_call(self).await;
let declined_call_str = stock_str::declined_call(self);
call.update_text(self, &declined_call_str).await?;
} else {
call.mark_as_canceled(self).await?;
let canceled_call_str = stock_str::canceled_call(self).await;
let canceled_call_str = stock_str::canceled_call(self);
call.update_text(self, &canceled_call_str).await?;
}
} else {
@@ -333,11 +331,11 @@ impl Context {
if !call.is_accepted() && !call.is_ended() {
if call.is_incoming() {
call.mark_as_canceled(&context).await?;
let missed_call_str = stock_str::missed_call(&context).await;
let missed_call_str = stock_str::missed_call(&context);
call.update_text(&context, &missed_call_str).await?;
} else {
call.mark_as_ended(&context).await?;
let canceled_call_str = stock_str::canceled_call(&context).await;
let canceled_call_str = stock_str::canceled_call(&context);
call.update_text(&context, &canceled_call_str).await?;
}
context.emit_msgs_changed(call.msg.chat_id, call_id);
@@ -363,12 +361,12 @@ impl Context {
if call.is_incoming() {
if call.is_stale() {
let missed_call_str = stock_str::missed_call(self).await;
let missed_call_str = stock_str::missed_call(self);
call.update_text(self, &missed_call_str).await?;
self.emit_incoming_msg(call.msg.chat_id, call_id); // notify missed call
} else {
let incoming_call_str =
stock_str::incoming_call(self, call.has_video_initially()).await;
stock_str::incoming_call(self, call.has_video_initially());
call.update_text(self, &incoming_call_str).await?;
self.emit_msgs_changed(call.msg.chat_id, call_id); // ringing calls are not additionally notified
let can_call_me = match who_can_call_me(self).await? {
@@ -409,8 +407,7 @@ impl Context {
));
}
} else {
let outgoing_call_str =
stock_str::outgoing_call(self, call.has_video_initially()).await;
let outgoing_call_str = stock_str::outgoing_call(self, call.has_video_initially());
call.update_text(self, &outgoing_call_str).await?;
self.emit_msgs_changed(call.msg.chat_id, call_id);
}
@@ -462,22 +459,22 @@ impl Context {
if call.is_incoming() {
if from_id == ContactId::SELF {
call.mark_as_ended(self).await?;
let declined_call_str = stock_str::declined_call(self).await;
let declined_call_str = stock_str::declined_call(self);
call.update_text(self, &declined_call_str).await?;
} else {
call.mark_as_canceled(self).await?;
let missed_call_str = stock_str::missed_call(self).await;
let missed_call_str = stock_str::missed_call(self);
call.update_text(self, &missed_call_str).await?;
}
} else {
// outgoing
if from_id == ContactId::SELF {
call.mark_as_canceled(self).await?;
let canceled_call_str = stock_str::canceled_call(self).await;
let canceled_call_str = stock_str::canceled_call(self);
call.update_text(self, &canceled_call_str).await?;
} else {
call.mark_as_ended(self).await?;
let declined_call_str = stock_str::declined_call(self).await;
let declined_call_str = stock_str::declined_call(self);
call.update_text(self, &declined_call_str).await?;
}
}

View File

@@ -476,7 +476,7 @@ impl ChatId {
/// Adds message "Messages are end-to-end encrypted".
pub(crate) async fn add_e2ee_notice(self, context: &Context, timestamp: i64) -> Result<()> {
let text = stock_str::messages_e2ee_info_msg(context).await;
let text = stock_str::messages_e2ee_info_msg(context);
add_info_msg_with_cmd(
context,
self,
@@ -669,7 +669,7 @@ SELECT id, rfc724_mid, pre_rfc724_mid, timestamp, ?, 1 FROM msgs WHERE chat_id=?
}
if chat.is_self_talk() {
let mut msg = Message::new_text(stock_str::self_deleted_msg_body(context).await);
let mut msg = Message::new_text(stock_str::self_deleted_msg_body(context));
add_device_msg(context, None, Some(&mut msg)).await?;
}
chatlist_events::emit_chatlist_changed(context);
@@ -1155,10 +1155,10 @@ SELECT id, rfc724_mid, pre_rfc724_mid, timestamp, ?, 1 FROM msgs WHERE chat_id=?
pub async fn get_encryption_info(self, context: &Context) -> Result<String> {
let chat = Chat::load_from_db(context, self).await?;
if !chat.is_encrypted(context).await? {
return Ok(stock_str::encr_none(context).await);
return Ok(stock_str::encr_none(context));
}
let mut ret = stock_str::messages_are_e2ee(context).await + "\n";
let mut ret = stock_str::messages_are_e2ee(context) + "\n";
for &contact_id in get_chat_contacts(context, self)
.await?
@@ -1392,7 +1392,7 @@ impl Chat {
.context(format!("Failed loading chat {chat_id} from database"))?;
if chat.id.is_archived_link() {
chat.name = stock_str::archived_chats(context).await;
chat.name = stock_str::archived_chats(context);
} else {
if chat.typ == Chattype::Single && chat.name.is_empty() {
// chat.name is set to contact.display_name on changes,
@@ -1416,9 +1416,9 @@ impl Chat {
chat.name = chat_name;
}
if chat.param.exists(Param::Selftalk) {
chat.name = stock_str::saved_messages(context).await;
chat.name = stock_str::saved_messages(context);
} else if chat.param.exists(Param::Devicetalk) {
chat.name = stock_str::device_messages(context).await;
chat.name = stock_str::device_messages(context);
}
}
@@ -2306,15 +2306,10 @@ pub(crate) async fn update_special_chat_names(context: &Context) -> Result<()> {
update_special_chat_name(
context,
ContactId::DEVICE,
stock_str::device_messages(context).await,
)
.await?;
update_special_chat_name(
context,
ContactId::SELF,
stock_str::saved_messages(context).await,
stock_str::device_messages(context),
)
.await?;
update_special_chat_name(context, ContactId::SELF, stock_str::saved_messages(context)).await?;
Ok(())
}
@@ -3068,7 +3063,7 @@ async fn donation_request_maybe(context: &Context) -> Result<()> {
let ts = if ts == 0 || msg_cnt.await? < 100 {
now.saturating_add(secs_between_checks)
} else {
let mut msg = Message::new_text(stock_str::donation_request(context).await);
let mut msg = Message::new_text(stock_str::donation_request(context));
add_device_msg(context, None, Some(&mut msg)).await?;
i64::MAX
};
@@ -3622,10 +3617,10 @@ pub(crate) async fn create_group_ex(
{
let text = if !grpid.is_empty() {
// Add "Others will only see this group after you sent a first message." message.
stock_str::new_group_send_first_message(context).await
stock_str::new_group_send_first_message(context)
} else {
// Add "Messages in this chat use classic email and are not encrypted." message.
stock_str::chat_unencrypted_explanation(context).await
stock_str::chat_unencrypted_explanation(context)
};
add_info_msg(context, chat_id, &text).await?;
}
@@ -4197,7 +4192,7 @@ async fn send_member_removal_msg(
if contact_id == ContactId::SELF {
if chat.typ == Chattype::InBroadcast {
msg.text = stock_str::msg_you_left_broadcast(context).await;
msg.text = stock_str::msg_you_left_broadcast(context);
} else {
msg.text = stock_str::msg_group_left_local(context, ContactId::SELF).await;
}
@@ -4358,7 +4353,7 @@ async fn rename_ex(
{
msg.viewtype = Viewtype::Text;
msg.text = if chat.typ == Chattype::OutBroadcast {
stock_str::msg_broadcast_name_changed(context, &chat.name, &new_name).await
stock_str::msg_broadcast_name_changed(context, &chat.name, &new_name)
} else {
stock_str::msg_grp_name(context, &chat.name, &new_name, ContactId::SELF).await
};
@@ -4423,7 +4418,7 @@ pub async fn set_chat_profile_image(
chat.param.remove(Param::ProfileImage);
msg.param.remove(Param::Arg);
msg.text = if chat.typ == Chattype::OutBroadcast {
stock_str::msg_broadcast_img_changed(context).await
stock_str::msg_broadcast_img_changed(context)
} else {
stock_str::msg_grp_img_deleted(context, ContactId::SELF).await
};
@@ -4437,7 +4432,7 @@ pub async fn set_chat_profile_image(
chat.param.set(Param::ProfileImage, image_blob.as_name());
msg.param.set(Param::Arg, image_blob.as_name());
msg.text = if chat.typ == Chattype::OutBroadcast {
stock_str::msg_broadcast_img_changed(context).await
stock_str::msg_broadcast_img_changed(context)
} else {
stock_str::msg_grp_img_changed(context, ContactId::SELF).await
};

View File

@@ -806,7 +806,7 @@ async fn test_self_talk() -> Result<()> {
assert!(chat.visibility == ChatVisibility::Normal);
assert!(!chat.is_device_talk());
assert!(chat.can_send(&t).await?);
assert_eq!(chat.name, stock_str::saved_messages(&t).await);
assert_eq!(chat.name, stock_str::saved_messages(&t));
assert!(chat.get_profile_image(&t).await?.is_some());
let msg_id = send_text_msg(&t, chat.id, "foo self".to_string()).await?;
@@ -911,7 +911,7 @@ async fn test_add_device_msg_labelled() -> Result<()> {
assert!(!chat.can_send(&t).await?);
assert!(chat.why_cant_send(&t).await? == Some(CantSendReason::DeviceChat));
assert_eq!(chat.name, stock_str::device_messages(&t).await);
assert_eq!(chat.name, stock_str::device_messages(&t));
let device_msg_icon = chat.get_profile_image(&t).await?.unwrap();
assert_eq!(
device_msg_icon.metadata()?.len(),
@@ -3797,7 +3797,7 @@ async fn test_leave_broadcast_multidevice() -> Result<()> {
assert_eq!(rcvd.chat_id, bob1_hello.chat_id);
assert!(rcvd.is_info());
assert_eq!(rcvd.get_info_type(), SystemMessage::MemberRemovedFromGroup);
assert_eq!(rcvd.text, stock_str::msg_you_left_broadcast(bob1).await);
assert_eq!(rcvd.text, stock_str::msg_you_left_broadcast(bob1));
Ok(())
}

View File

@@ -417,7 +417,7 @@ impl Chatlist {
Summary::new_with_reaction_details(context, &lastmsg, chat, lastcontact.as_ref()).await
} else {
Ok(Summary {
text: stock_str::no_messages(context).await,
text: stock_str::no_messages(context),
..Default::default()
})
}
@@ -648,7 +648,6 @@ mod tests {
assert_eq!(chats.len(), 0);
t.set_stock_translation(StockMessage::SavedMessages, "test-1234-save".to_string())
.await
.unwrap();
let chats = Chatlist::try_load(&t, 0, Some("t-1234-s"), None)
.await
@@ -656,7 +655,6 @@ mod tests {
assert_eq!(chats.len(), 1);
t.set_stock_translation(StockMessage::DeviceMessages, "test-5678-babbel".to_string())
.await
.unwrap();
let chats = Chatlist::try_load(&t, 0, Some("t-5678-b"), None)
.await

View File

@@ -246,8 +246,7 @@ async fn test_sync() -> Result<()> {
alice1
.get_config(Config::Selfavatar)
.await?
.filter(|path| path.ends_with(".png"))
.is_some()
.is_some_and(|path| path.ends_with(".png"))
);
alice0.set_config(Config::Selfavatar, None).await?;
sync(&alice0, &alice1).await;
@@ -305,16 +304,14 @@ async fn test_no_sync_on_self_sent_msg() -> Result<()> {
alice1
.get_config(Config::Selfavatar)
.await?
.filter(|path| path.ends_with(".jpg"))
.is_some()
.is_some_and(|path| path.ends_with(".jpg"))
);
sync(alice1, alice0).await;
assert!(
alice0
.get_config(Config::Selfavatar)
.await?
.filter(|path| path.ends_with(".jpg"))
.is_some()
.is_some_and(|path| path.ends_with(".jpg"))
);
Ok(())

View File

@@ -146,7 +146,7 @@ impl Context {
if let Err(err) = res.as_ref() {
// We are using Anyhow's .context() and to show the
// inner error, too, we need the {:#}:
let error_msg = stock_str::configuration_failed(self, &format!("{err:#}")).await;
let error_msg = stock_str::configuration_failed(self, &format!("{err:#}"));
progress!(self, 0, Some(error_msg.clone()));
bail!(error_msg);
} else {
@@ -637,10 +637,7 @@ async fn configure(ctx: &Context, param: &EnteredLoginParam) -> Result<Option<&'
let imap_session = match imap.connect(ctx, configuring).await {
Ok(imap_session) => imap_session,
Err(err) => {
bail!(
"{}",
nicer_configuration_error(ctx, format!("{err:#}")).await
);
bail!("{}", nicer_configuration_error(ctx, format!("{err:#}")));
}
};
@@ -781,7 +778,7 @@ async fn get_autoconfig(
None
}
async fn nicer_configuration_error(context: &Context, e: String) -> String {
fn nicer_configuration_error(context: &Context, e: String) -> String {
if e.to_lowercase().contains("could not resolve")
|| e.to_lowercase().contains("connection attempts")
|| e.to_lowercase()
@@ -790,7 +787,7 @@ async fn nicer_configuration_error(context: &Context, e: String) -> String {
|| e.to_lowercase()
.contains("failed to lookup address information")
{
return stock_str::error_no_network(context).await;
return stock_str::error_no_network(context);
}
e

View File

@@ -688,7 +688,7 @@ impl Contact {
.await?
{
if contact_id == ContactId::SELF {
contact.name = stock_str::self_msg(context).await;
contact.name = stock_str::self_msg(context);
contact.authname = context
.get_config(Config::Displayname)
.await?
@@ -705,9 +705,9 @@ impl Contact {
.await?
.unwrap_or_default();
} else if contact_id == ContactId::DEVICE {
contact.name = stock_str::device_messages(context).await;
contact.name = stock_str::device_messages(context);
contact.addr = ContactId::DEVICE_ADDR.to_string();
contact.status = stock_str::device_messages_hint(context).await;
contact.status = stock_str::device_messages_hint(context);
}
Ok(Some(contact))
} else {
@@ -1240,7 +1240,7 @@ ORDER BY c.origin>=? DESC, c.last_seen DESC, c.id DESC
if self_addr.contains(query)
|| self_name.contains(query)
|| self_name2.await.contains(query)
|| self_name2.contains(query)
{
add_self = true;
}
@@ -1392,17 +1392,17 @@ WHERE addr=?
.unwrap_or_default();
let Some(fingerprint_other) = contact.fingerprint() else {
return Ok(stock_str::encr_none(context).await);
return Ok(stock_str::encr_none(context));
};
let fingerprint_other = fingerprint_other.to_string();
let stock_message = if contact.public_key(context).await?.is_some() {
stock_str::messages_are_e2ee(context).await
stock_str::messages_are_e2ee(context)
} else {
stock_str::encr_none(context).await
stock_str::encr_none(context)
};
let finger_prints = stock_str::finger_prints(context).await;
let finger_prints = stock_str::finger_prints(context);
let mut ret = format!("{stock_message}\n{finger_prints}:");
let fingerprint_self = load_self_public_key(context)
@@ -1412,7 +1412,7 @@ WHERE addr=?
if addr < contact.addr {
cat_fingerprint(
&mut ret,
&stock_str::self_msg(context).await,
&stock_str::self_msg(context),
&addr,
&fingerprint_self,
);
@@ -1431,7 +1431,7 @@ WHERE addr=?
);
cat_fingerprint(
&mut ret,
&stock_str::self_msg(context).await,
&stock_str::self_msg(context),
&addr,
&fingerprint_self,
);

View File

@@ -282,7 +282,7 @@ async fn test_add_or_lookup() {
// check SELF
let contact = Contact::get_by_id(&t, ContactId::SELF).await.unwrap();
assert_eq!(contact.get_name(), stock_str::self_msg(&t).await);
assert_eq!(contact.get_name(), stock_str::self_msg(&t));
assert_eq!(contact.get_addr(), "alice@example.org");
assert!(!contact.is_blocked());
}

View File

@@ -266,15 +266,11 @@ fn dehtml_endtag_cb(event: &BytesEnd, dehtml: &mut Dehtml) {
}
}
}
"b" | "strong" => {
if dehtml.get_add_text() != AddText::No {
*dehtml.get_buf() += "*";
}
"b" | "strong" if dehtml.get_add_text() != AddText::No => {
*dehtml.get_buf() += "*";
}
"i" | "em" => {
if dehtml.get_add_text() != AddText::No {
*dehtml.get_buf() += "_";
}
"i" | "em" if dehtml.get_add_text() != AddText::No => {
*dehtml.get_buf() += "_";
}
"blockquote" => pop_tag(&mut dehtml.blockquotes_since_blockquote),
_ => {}
@@ -341,15 +337,11 @@ fn dehtml_starttag_cb<B: std::io::BufRead>(
}
}
}
"b" | "strong" => {
if dehtml.get_add_text() != AddText::No {
*dehtml.get_buf() += "*";
}
"b" | "strong" if dehtml.get_add_text() != AddText::No => {
*dehtml.get_buf() += "*";
}
"i" | "em" => {
if dehtml.get_add_text() != AddText::No {
*dehtml.get_buf() += "_";
}
"i" | "em" if dehtml.get_add_text() != AddText::No => {
*dehtml.get_buf() += "_";
}
"blockquote" => dehtml.blockquotes_since_blockquote += 1,
_ => {}

View File

@@ -125,7 +125,6 @@ pub enum HeaderDef {
/// [Autocrypt](https://autocrypt.org/) header.
Autocrypt,
AutocryptGossip,
AutocryptSetupMessage,
SecureJoin,
/// Deprecated header containing Group-ID in `vg-request-with-auth` message.

View File

@@ -296,6 +296,11 @@ impl Imap {
Ok(imap)
}
/// Returns transport ID of the IMAP client.
pub fn transport_id(&self) -> u32 {
self.transport_id
}
/// Connects to IMAP server and returns a new IMAP session.
///
/// Calling this function is not enough to perform IMAP operations. Use [`Imap::prepare`]
@@ -318,7 +323,8 @@ impl Imap {
if !ratelimit_duration.is_zero() {
warn!(
context,
"IMAP got rate limited, waiting for {} until can connect.",
"Transport {}: IMAP got rate limited, waiting for {} until can connect.",
self.transport_id,
duration_to_str(ratelimit_duration),
);
let interrupted = async {
@@ -330,12 +336,16 @@ impl Imap {
if interrupted {
info!(
context,
"Connecting to IMAP without waiting for ratelimit due to interrupt."
"Transport {}: Connecting to IMAP without waiting for ratelimit due to interrupt.",
self.transport_id
);
}
}
info!(context, "Connecting to IMAP server.");
info!(
context,
"Transport {}: Connecting to IMAP server.", self.transport_id
);
self.connectivity.set_connecting(context);
self.conn_last_try = tools::Time::now();
@@ -350,7 +360,10 @@ impl Imap {
let login_params = prioritize_server_login_params(&context.sql, &self.lp, "imap").await?;
let mut first_error = None;
for lp in login_params {
info!(context, "IMAP trying to connect to {}.", &lp.connection);
info!(
context,
"Transport {}: IMAP trying to connect to {}.", self.transport_id, &lp.connection
);
let connection_candidate = lp.connection.clone();
let client = match Client::connect(
context,
@@ -398,7 +411,10 @@ impl Imap {
let resync_request_sender = self.resync_request_sender.clone();
let session = if capabilities.can_compress {
info!(context, "Enabling IMAP compression.");
info!(
context,
"Transport {}: Enabling IMAP compression.", self.transport_id
);
let compressed_session = session
.compress(|s| {
let session_stream: Box<dyn SessionStream> = Box::new(s);
@@ -431,15 +447,21 @@ impl Imap {
lp.user
)));
self.connectivity.set_preparing(context);
info!(context, "Successfully logged into IMAP server.");
info!(
context,
"Transport {}: Successfully logged into IMAP server.", self.transport_id
);
return Ok(session);
}
Err(err) => {
let imap_user = lp.user.to_owned();
let message = stock_str::cannot_login(context, &imap_user).await;
let message = stock_str::cannot_login(context, &imap_user);
warn!(context, "IMAP failed to login: {err:#}.");
warn!(
context,
"Transport {}: IMAP failed to login: {err:#}.", self.transport_id
);
first_error.get_or_insert(format_err!("{message} ({err:#})"));
// If it looks like the password is wrong, send a notification:
@@ -458,7 +480,11 @@ impl Imap {
)
.await
{
warn!(context, "Failed to add device message: {e:#}.");
warn!(
context,
"Transport {}: Failed to add device message: {e:#}.",
self.transport_id
);
} else {
context
.set_config_internal(Config::NotifyAboutWrongPw, None)
@@ -520,10 +546,21 @@ impl Imap {
bail!("IMAP operation attempted while it is torn down");
}
let transport_id = session.transport_id();
info!(
context,
"Transport {transport_id}: fetch_move_delete start."
);
let msgs_fetched = self
.fetch_new_messages(context, session, watch_folder, folder_meaning)
.await
.context("fetch_new_messages")?;
info!(
context,
"Transport {transport_id}: fetch_move_delete finished fetch_new_messages."
);
if msgs_fetched && context.get_config_delete_device_after().await?.is_some() {
// New messages were fetched and shall be deleted later, restart ephemeral loop.
// Note that the `Config::DeleteDeviceAfter` timer starts as soon as the messages are
@@ -551,19 +588,35 @@ impl Imap {
folder: &str,
folder_meaning: FolderMeaning,
) -> Result<bool> {
let transport_id = session.transport_id();
if should_ignore_folder(context, folder, folder_meaning).await? {
info!(context, "Not fetching from {folder:?}.");
info!(
context,
"Transport {transport_id}: Not fetching from {folder:?}."
);
session.new_mail = false;
return Ok(false);
}
info!(
context,
"Transport {transport_id}: fetch_new_messages selects folder {folder:?}."
);
let folder_exists = session
.select_with_uidvalidity(context, folder)
.await
.with_context(|| format!("Failed to select folder {folder:?}"))?;
info!(
context,
"Transport {transport_id}: fetch_new_messages selected folder {folder:?}."
);
if !session.new_mail {
info!(context, "No new emails in folder {folder:?}.");
info!(
context,
"Transport {transport_id}: No new emails in folder {folder:?}."
);
return Ok(false);
}
// Make sure not to return before setting new_mail to false
@@ -1091,6 +1144,7 @@ impl Session {
}
let transport_id = self.transport_id();
info!(context, "Transport {transport_id}: Storing seen flags.");
let rows = context
.sql
.query_map_vec(
@@ -1125,13 +1179,15 @@ impl Session {
} else if let Err(err) = self.add_flag_finalized_with_set(&uid_set, "\\Seen").await {
warn!(
context,
"Cannot mark messages {uid_set} in {folder} as seen, will retry later: {err:#}."
"Transport {transport_id}: Cannot mark messages {uid_set} in {folder} as seen, will retry later: {err:#}."
);
continue;
} else {
info!(
context,
"Marked messages {} in folder {} as seen.", uid_set, folder
"Transport {transport_id}: Marked messages {} in folder {} as seen.",
uid_set,
folder
);
}
context
@@ -1146,6 +1202,10 @@ impl Session {
.await
.context("Cannot remove messages marked as seen from imap_markseen table")?;
}
info!(
context,
"Transport {transport_id}: Finished storing seen flags."
);
Ok(())
}
@@ -1486,9 +1546,10 @@ impl Session {
return Ok(());
}
let transport_id = self.transport_id();
info!(
context,
"Server supports metadata, retrieving server comment and admin contact."
"Transport {transport_id}: Server supports metadata, retrieving server comment and admin contact."
);
let mut comment = None;
@@ -1521,7 +1582,8 @@ impl Session {
} else {
warn!(
context,
"Got invalid URL from iroh relay metadata: {:?}.", value
"Transport {transport_id}: Got invalid URL from iroh relay metadata: {:?}.",
value
);
}
}
@@ -1550,6 +1612,7 @@ impl Session {
create_fallback_ice_servers()
};
info!(context, "Transport {transport_id}: Got IMAP metadata.");
*lock = Some(ServerMetadata {
comment,
admin,
@@ -1566,11 +1629,18 @@ impl Session {
return Ok(());
}
let transport_id = self.transport_id();
let Some(device_token) = context.push_subscriber.device_token().await else {
return Ok(());
};
if self.can_metadata() && self.can_push() {
info!(
context,
"Transport {transport_id}: Subscribing for push notifications."
);
let old_encrypted_device_token =
context.get_config(Config::EncryptedDeviceToken).await?;

View File

@@ -27,6 +27,8 @@ impl Session {
idle_interrupt_receiver: Receiver<()>,
folder: &str,
) -> Result<Self> {
let transport_id = self.transport_id();
self.select_with_uidvalidity(context, folder).await?;
if self.drain_unsolicited_responses(context)? {
@@ -36,13 +38,16 @@ impl Session {
if self.new_mail {
info!(
context,
"Skipping IDLE in {folder:?} because there may be new mail."
"Transport {transport_id}: Skipping IDLE in {folder:?} because there may be new mail."
);
return Ok(self);
}
if let Ok(()) = idle_interrupt_receiver.try_recv() {
info!(context, "Skip IDLE in {folder:?} because we got interrupt.");
info!(
context,
"Transport {transport_id}: Skip IDLE in {folder:?} because we got interrupt."
);
return Ok(self);
}
@@ -61,7 +66,7 @@ impl Session {
info!(
context,
"IDLE entering wait-on-remote state in folder {folder:?}."
"Transport {transport_id}: IDLE entering wait-on-remote state in folder {folder:?}."
);
// Spawn a task to relay interrupts from `idle_interrupt_receiver`

View File

@@ -71,13 +71,18 @@ impl ImapSession {
self.select(folder).await
};
let transport_id = self.transport_id();
// <https://tools.ietf.org/html/rfc3501#section-6.3.1>
// says that if the server reports select failure we are in
// authenticated (not-select) state.
match res {
Ok(mailbox) => {
info!(context, "Selected folder {folder:?}.");
info!(
context,
"Transport {transport_id}: Selected folder {folder:?}."
);
self.selected_folder = Some(folder.to_string());
self.selected_mailbox = Some(mailbox);
Ok(NewlySelected::Yes)

View File

@@ -208,7 +208,7 @@ impl BackupProvider {
info!(context, "Received backup reception acknowledgement.");
context.emit_event(EventType::ImexProgress(1000));
let mut msg = Message::new_text(backup_transfer_msg_body(&context).await);
let mut msg = Message::new_text(backup_transfer_msg_body(&context));
add_device_msg(&context, None, Some(&mut msg)).await?;
Ok(())

View File

@@ -288,13 +288,13 @@ pub async fn send_locations_to_chat(
)
.await?;
if 0 != seconds && !is_sending_locations_before {
let mut msg = Message::new_text(stock_str::msg_location_enabled(context).await);
let mut msg = Message::new_text(stock_str::msg_location_enabled(context));
msg.param.set_cmd(SystemMessage::LocationStreamingEnabled);
chat::send_msg(context, chat_id, &mut msg)
.await
.unwrap_or_default();
} else if 0 == seconds && is_sending_locations_before {
let stock_str = stock_str::msg_location_disabled(context).await;
let stock_str = stock_str::msg_location_disabled(context);
chat::add_info_msg(context, chat_id, &stock_str).await?;
}
context.emit_event(EventType::ChatModified(chat_id));
@@ -852,7 +852,7 @@ async fn maybe_send_locations(context: &Context) -> Result<Option<u64>> {
.await
.context("failed to disable location streaming")?;
let stock_str = stock_str::msg_location_disabled(context).await;
let stock_str = stock_str::msg_location_disabled(context);
chat::add_info_msg(context, chat_id, &stock_str).await?;
context.emit_event(EventType::ChatModified(chat_id));
chatlist_events::emit_chatlist_item_changed(context, chat_id);

View File

@@ -598,7 +598,7 @@ impl Message {
if let Some(msg) = &mut msg {
msg.additional_text =
Self::get_additional_text(context, msg.download_state, &msg.param).await?;
Self::get_additional_text(context, msg.download_state, &msg.param)?;
}
Ok(msg)
@@ -607,7 +607,7 @@ impl Message {
/// Returns additional text which is appended to the message's text field
/// when it is loaded from the database.
/// Currently this is used to add infomation to pre-messages of what the download will be and how large it is
async fn get_additional_text(
fn get_additional_text(
context: &Context,
download_state: DownloadState,
param: &Params,
@@ -630,7 +630,7 @@ impl Message {
return match viewtype {
Viewtype::File => Ok(format!(" [{file_name} {file_size}]")),
_ => {
let translated_viewtype = viewtype.to_locale_string(context).await;
let translated_viewtype = viewtype.to_locale_string(context);
Ok(format!(" [{translated_viewtype} {file_size}]"))
}
};

View File

@@ -732,7 +732,7 @@ impl MimeFactory {
Some(name) => name,
None => context.get_config(Config::Addr).await?.unwrap_or_default(),
};
stock_str::subject_for_new_contact(context, self_name).await
stock_str::subject_for_new_contact(context, self_name)
}
Loaded::Mdn { .. } => "Receipt Notification".to_string(), // untranslated to no reveal sender's language
};

View File

@@ -262,8 +262,6 @@ pub enum SystemMessage {
GroupDescriptionChanged = 70,
}
const MIME_AC_SETUP_FILE: &str = "application/autocrypt-setup";
impl MimeMessage {
/// Parse a mime message.
///
@@ -745,20 +743,8 @@ impl MimeMessage {
}
/// Parses system messages.
fn parse_system_message_headers(&mut self, context: &Context) {
if self.get_header(HeaderDef::AutocryptSetupMessage).is_some() && !self.incoming {
self.parts.retain(|part| {
part.mimetype
.as_ref()
.is_none_or(|mimetype| mimetype.as_ref() == MIME_AC_SETUP_FILE)
});
if self.parts.len() == 1 {
self.is_system_message = SystemMessage::AutocryptSetupMessage;
} else {
warn!(context, "could not determine ASM mime-part");
}
} else if let Some(value) = self.get_header(HeaderDef::ChatContent) {
fn parse_system_message_headers(&mut self) {
if let Some(value) = self.get_header(HeaderDef::ChatContent) {
if value == "location-streaming-enabled" {
self.is_system_message = SystemMessage::LocationStreamingEnabled;
} else if value == "ephemeral-timer-changed" {
@@ -908,7 +894,7 @@ impl MimeMessage {
}
async fn parse_headers(&mut self, context: &Context) -> Result<()> {
self.parse_system_message_headers(context);
self.parse_system_message_headers();
self.parse_avatar_headers(context)?;
self.parse_videochat_headers();
if self.delivery_report.is_none() {

View File

@@ -881,7 +881,7 @@ fn merge_with_cache(
) -> Vec<SocketAddr> {
let rest = resolved_addrs.split_off(std::cmp::min(resolved_addrs.len(), 2));
for addr in cache.into_iter().chain(rest.into_iter()) {
for addr in cache.into_iter().chain(rest) {
if !resolved_addrs.contains(&addr) {
resolved_addrs.push(addr);
if resolved_addrs.len() >= 10 {

View File

@@ -136,6 +136,7 @@ impl PushSubscriber {
return Ok(());
};
info!(context, "Subscribing for heartbeat notifications.");
if http::post_string(
context,
"https://notifications.delta.chat/register",
@@ -143,6 +144,7 @@ impl PushSubscriber {
)
.await?
{
info!(context, "Subscribed for heartbeat notifications.");
state.heartbeat_subscribed = true;
}
Ok(())

View File

@@ -111,10 +111,10 @@ async fn generate_join_group_qr_code(context: &Context, chat_id: ChatId) -> Resu
let qrcode_description = match chat.typ {
crate::constants::Chattype::Group => {
stock_str::secure_join_group_qr_description(context, &chat).await
stock_str::secure_join_group_qr_description(context, &chat)
}
crate::constants::Chattype::OutBroadcast => {
stock_str::secure_join_broadcast_qr_description(context, &chat).await
stock_str::secure_join_broadcast_qr_description(context, &chat)
}
_ => bail!("Unexpected chat type {}", chat.typ),
};
@@ -132,7 +132,7 @@ async fn generate_verification_qr(context: &Context) -> Result<String> {
let (avatar, displayname, addr, color) = self_info(context).await?;
inner_generate_secure_join_qr_code(
&stock_str::setup_contact_qr_description(context, &displayname, &addr).await,
&stock_str::setup_contact_qr_description(context, &displayname, &addr),
&securejoin::get_securejoin_qr(context, None).await?,
&color,
avatar,

View File

@@ -109,10 +109,9 @@ impl Context {
/// called.
pub(crate) async fn quota_needs_update(&self, transport_id: u32, ratelimit_secs: u64) -> bool {
let quota = self.quota.read().await;
quota
.get(&transport_id)
.filter(|quota| time_elapsed(&quota.modified) < Duration::from_secs(ratelimit_secs))
.is_none()
quota.get(&transport_id).is_none_or(|quota| {
time_elapsed(&quota.modified) >= Duration::from_secs(ratelimit_secs)
})
}
/// Updates `quota.recent`, sets `quota.modified` to the current time
@@ -124,11 +123,15 @@ impl Context {
/// in case for some providers the quota is always at ~100%
/// and new space is allocated as needed.
pub(crate) async fn update_recent_quota(&self, session: &mut ImapSession) -> Result<()> {
let transport_id = session.transport_id();
info!(self, "Transport {transport_id}: Updating quota.");
let quota = if session.can_check_quota() {
let folders = get_watched_folders(self).await?;
get_unique_quota_roots_and_usage(session, folders).await
} else {
Err(anyhow!(stock_str::not_supported_by_provider(self).await))
Err(anyhow!(stock_str::not_supported_by_provider(self)))
};
if let Ok(quota) = &quota {
@@ -143,26 +146,29 @@ impl Context {
Some(&highest.to_string()),
)
.await?;
let mut msg =
Message::new_text(stock_str::quota_exceeding(self, highest).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, "cannot get highest quota usage: {:#}", err),
Err(err) => warn!(
self,
"Transport {transport_id}: Cannot get highest quota usage: {err:#}"
),
}
}
self.quota.write().await.insert(
session.transport_id(),
transport_id,
QuotaInfo {
recent: quota,
modified: tools::Time::now(),
},
);
info!(self, "Transport {transport_id}: Updated quota.");
self.emit_event(EventType::ConnectivityChanged);
Ok(())
}

View File

@@ -347,8 +347,7 @@ impl Chat {
if self
.param
.get_i64(Param::LastReactionTimestamp)
.filter(|&reaction_timestamp| reaction_timestamp > timestamp)
.is_none()
.is_none_or(|reaction_timestamp| reaction_timestamp <= timestamp)
{
return Ok(None);
};

View File

@@ -879,8 +879,7 @@ UPDATE config SET value=? WHERE keyname='configured_addr' AND value!=?1
let instance = if mime_parser
.parts
.first()
.filter(|part| part.typ == Viewtype::Webxdc)
.is_some()
.is_some_and(|part| part.typ == Viewtype::Webxdc)
{
can_info_msg = false;
Some(
@@ -3357,7 +3356,7 @@ async fn apply_chat_name_avatar_and_description_changes(
let old_name = &sanitize_single_line(old_name);
better_msg.get_or_insert(
if matches!(chat.typ, Chattype::InBroadcast | Chattype::OutBroadcast) {
stock_str::msg_broadcast_name_changed(context, old_name, grpname).await
stock_str::msg_broadcast_name_changed(context, old_name, grpname)
} else {
stock_str::msg_grp_name(context, old_name, grpname, from_id).await
},
@@ -3420,7 +3419,7 @@ async fn apply_chat_name_avatar_and_description_changes(
// apart from that, the group-avatar is send along with various other messages
better_msg.get_or_insert(
if matches!(chat.typ, Chattype::InBroadcast | Chattype::OutBroadcast) {
stock_str::msg_broadcast_img_changed(context).await
stock_str::msg_broadcast_img_changed(context)
} else {
match avatar_action {
AvatarAction::Delete => stock_str::msg_grp_img_deleted(context, from_id).await,
@@ -3860,7 +3859,7 @@ async fn apply_in_broadcast_changes(
info!(context, "No-op broadcast 'Member added' message (TRASH)");
"".to_string()
} else {
stock_str::msg_you_joined_broadcast(context).await
stock_str::msg_you_joined_broadcast(context)
};
better_msg.get_or_insert(msg);
@@ -3876,7 +3875,7 @@ async fn apply_in_broadcast_changes(
chat::delete_broadcast_secret(context, chat.id).await?;
if from_id == ContactId::SELF {
better_msg.get_or_insert(stock_str::msg_you_left_broadcast(context).await);
better_msg.get_or_insert(stock_str::msg_you_left_broadcast(context));
} else {
better_msg.get_or_insert(
stock_str::msg_del_member_local(context, ContactId::SELF, from_id).await,

View File

@@ -355,6 +355,7 @@ async fn inbox_loop(
stop_token,
} = inbox_handlers;
let transport_id = connection.transport_id();
let ctx1 = ctx.clone();
let fut = async move {
let ctx = ctx1;
@@ -368,23 +369,34 @@ async fn inbox_loop(
let session = if let Some(session) = old_session.take() {
session
} else {
info!(ctx, "Preparing new IMAP session for inbox.");
info!(
ctx,
"Transport {transport_id}: Preparing new IMAP session for inbox."
);
match connection.prepare(&ctx).await {
Err(err) => {
warn!(ctx, "Failed to prepare inbox connection: {err:#}.");
warn!(
ctx,
"Transport {transport_id}: Failed to prepare inbox connection: {err:#}."
);
continue;
}
Ok(session) => session,
Ok(session) => {
info!(
ctx,
"Transport {transport_id}: Prepared new IMAP session for inbox."
);
session
}
}
};
match inbox_fetch_idle(&ctx, &mut connection, session).await {
Err(err) => warn!(ctx, "Failed inbox fetch_idle: {err:#}."),
Err(err) => warn!(
ctx,
"Transport {transport_id}: Failed inbox fetch_idle: {err:#}."
),
Ok(session) => {
info!(
ctx,
"IMAP loop iteration for inbox finished, keeping the session."
);
old_session = Some(session);
}
}
@@ -394,7 +406,7 @@ async fn inbox_loop(
stop_token
.cancelled()
.map(|_| {
info!(ctx, "Shutting down inbox loop.");
info!(ctx, "Transport {transport_id}: Shutting down inbox loop.");
})
.race(fut)
.await;
@@ -431,17 +443,25 @@ pub async fn convert_folder_meaning(
}
async fn inbox_fetch_idle(ctx: &Context, imap: &mut Imap, mut session: Session) -> Result<Session> {
let transport_id = session.transport_id();
// Update quota no more than once a minute.
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);
warn!(
ctx,
"Transport {transport_id}: Failed to update quota: {err:#}."
);
}
if let Ok(()) = imap.resync_request_receiver.try_recv()
&& let Err(err) = session.resync_folders(ctx).await
{
warn!(ctx, "Failed to resync folders: {:#}.", err);
warn!(
ctx,
"Transport {transport_id}: Failed to resync folders: {err:#}."
);
imap.resync_request_sender.try_send(()).ok();
}
@@ -456,7 +476,10 @@ async fn inbox_fetch_idle(ctx: &Context, imap: &mut Imap, mut session: Session)
}
}
Err(err) => {
warn!(ctx, "Failed to get last housekeeping time: {}", err);
warn!(
ctx,
"Transport {transport_id}: Failed to get last housekeeping time: {err:#}"
);
}
};
@@ -486,6 +509,8 @@ async fn fetch_idle(
mut session: Session,
folder_meaning: FolderMeaning,
) -> Result<Session> {
let transport_id = session.transport_id();
let Some((folder_config, watch_folder)) = convert_folder_meaning(ctx, folder_meaning).await?
else {
// The folder is not configured.
@@ -537,7 +562,7 @@ async fn fetch_idle(
if !session.can_idle() {
info!(
ctx,
"IMAP session does not support IDLE, going to fake idle."
"Transport {transport_id}: IMAP session does not support IDLE, going to fake idle."
);
connection.fake_idle(ctx, watch_folder).await?;
return Ok(session);
@@ -550,15 +575,14 @@ async fn fetch_idle(
.log_err(ctx)
.unwrap_or_default()
{
info!(ctx, "IMAP IDLE is disabled, going to fake idle.");
info!(
ctx,
"Transport {transport_id}: IMAP IDLE is disabled, going to fake idle."
);
connection.fake_idle(ctx, watch_folder).await?;
return Ok(session);
}
info!(
ctx,
"IMAP session in folder {watch_folder:?} supports IDLE, using it."
);
let session = session
.idle(
ctx,
@@ -619,10 +643,6 @@ async fn simple_imap_loop(
match fetch_idle(&ctx, &mut connection, session, folder_meaning).await {
Err(err) => warn!(ctx, "Failed fetch_idle: {err:#}"),
Ok(session) => {
info!(
ctx,
"IMAP loop iteration for {folder_meaning} finished, keeping the session"
);
old_session = Some(session);
}
}
@@ -876,7 +896,7 @@ impl Scheduler {
let timeout_duration = std::time::Duration::from_secs(30);
let tracker = TaskTracker::new();
for b in self.inboxes.into_iter().chain(self.oboxes.into_iter()) {
for b in self.inboxes.into_iter().chain(self.oboxes) {
let context = context.clone();
tracker.spawn(async move {
tokio::time::timeout(timeout_duration, b.handle)

View File

@@ -109,36 +109,36 @@ impl DetailedConnectivity {
}
}
async fn to_string_imap(&self, context: &Context) -> String {
fn to_string_imap(&self, context: &Context) -> String {
match self {
DetailedConnectivity::Error(e) => stock_str::error(context, e).await,
DetailedConnectivity::Error(e) => stock_str::error(context, e),
DetailedConnectivity::Uninitialized => "Not started".to_string(),
DetailedConnectivity::Connecting => stock_str::connecting(context).await,
DetailedConnectivity::Connecting => stock_str::connecting(context),
DetailedConnectivity::Preparing | DetailedConnectivity::Working => {
stock_str::updating(context).await
stock_str::updating(context)
}
DetailedConnectivity::InterruptingIdle | DetailedConnectivity::Idle => {
stock_str::connected(context).await
stock_str::connected(context)
}
DetailedConnectivity::NotConfigured => "Not configured".to_string(),
}
}
async fn to_string_smtp(&self, context: &Context) -> String {
fn to_string_smtp(&self, context: &Context) -> String {
match self {
DetailedConnectivity::Error(e) => stock_str::error(context, e).await,
DetailedConnectivity::Error(e) => stock_str::error(context, e),
DetailedConnectivity::Uninitialized => {
"You did not try to send a message recently.".to_string()
}
DetailedConnectivity::Connecting => stock_str::connecting(context).await,
DetailedConnectivity::Working => stock_str::sending(context).await,
DetailedConnectivity::Connecting => stock_str::connecting(context),
DetailedConnectivity::Working => stock_str::sending(context),
// We don't know any more than that the last message was sent successfully;
// since sending the last message, connectivity could have changed, which we don't notice
// until another message is sent
DetailedConnectivity::InterruptingIdle
| DetailedConnectivity::Preparing
| DetailedConnectivity::Idle => stock_str::last_msg_sent_successfully(context).await,
| DetailedConnectivity::Idle => stock_str::last_msg_sent_successfully(context),
DetailedConnectivity::NotConfigured => "Not configured".to_string(),
}
}
@@ -369,8 +369,8 @@ impl Context {
.get_config_bool(crate::config::Config::ProxyEnabled)
.await?
{
let proxy_enabled = stock_str::proxy_enabled(self).await;
let proxy_description = stock_str::proxy_description(self).await;
let proxy_enabled = stock_str::proxy_enabled(self);
let proxy_description = stock_str::proxy_description(self);
ret += &format!("<h3>{proxy_enabled}</h3><ul><li>{proxy_description}</li></ul>");
}
@@ -396,7 +396,7 @@ impl Context {
_ => {
ret += &format!(
"<h3>{}</h3>\n</body></html>\n",
stock_str::not_connected(self).await
stock_str::not_connected(self)
);
return Ok(ret);
}
@@ -412,7 +412,7 @@ impl Context {
// =============================================================================================
let watched_folders = get_watched_folder_configs(self).await?;
let incoming_messages = stock_str::incoming_messages(self).await;
let incoming_messages = stock_str::incoming_messages(self);
ret += &format!("<h3>{incoming_messages}</h3><ul>");
let transports = self
@@ -449,7 +449,7 @@ impl Context {
ret += &*escaper::encode_minimal(&foldername);
}
ret += ":</b> ";
ret += &*escaper::encode_minimal(&detailed.to_string_imap(self).await);
ret += &*escaper::encode_minimal(&detailed.to_string_imap(self));
ret += "<br />";
folder_added = true;
@@ -464,7 +464,7 @@ impl Context {
ret += &*detailed.to_icon();
ret += " ";
ret += &*escaper::encode_minimal(&detailed.to_string_imap(self).await);
ret += &*escaper::encode_minimal(&detailed.to_string_imap(self));
ret += "<br />";
}
}
@@ -504,13 +504,12 @@ impl Context {
);
}
let messages = stock_str::messages(self).await;
let messages = stock_str::messages(self);
let part_of_total_used = stock_str::part_of_total_used(
self,
&resource.usage.to_string(),
&resource.limit.to_string(),
)
.await;
);
ret += &match &resource.name {
Atom(resource_name) => {
format!(
@@ -531,7 +530,7 @@ impl Context {
// - most times, this is the only item anyway
let usage = &format_size(resource.usage * 1024, BINARY);
let limit = &format_size(resource.limit * 1024, BINARY);
stock_str::part_of_total_used(self, usage, limit).await
stock_str::part_of_total_used(self, usage, limit)
}
};
@@ -565,12 +564,12 @@ impl Context {
// Your last message was sent successfully
// =============================================================================================
let outgoing_messages = stock_str::outgoing_messages(self).await;
let outgoing_messages = stock_str::outgoing_messages(self);
ret += &format!("<h3>{outgoing_messages}</h3><ul><li>");
let detailed = smtp.get_detailed();
ret += &*detailed.to_icon();
ret += " ";
ret += &*escaper::encode_minimal(&detailed.to_string_smtp(self).await);
ret += &*escaper::encode_minimal(&detailed.to_string_smtp(self));
ret += "</li></ul>";
// =============================================================================================

View File

@@ -158,7 +158,7 @@ pub(super) async fn start_protocol(context: &Context, invite: QrInvite) -> Resul
chat::add_info_msg_with_cmd(
context,
private_chat_id,
&stock_str::securejoin_wait(context).await,
&stock_str::securejoin_wait(context),
SystemMessage::SecurejoinWait,
None,
time(),

View File

@@ -109,10 +109,10 @@ async fn test_setup_contact_ex(case: SetupContactCase) {
let mut i = 0..msg_cnt;
let msg = get_chat_msg(&bob, bob_chat.get_id(), i.next().unwrap(), msg_cnt).await;
assert!(msg.is_info());
assert_eq!(msg.get_text(), messages_e2ee_info_msg(&bob).await);
assert_eq!(msg.get_text(), messages_e2ee_info_msg(&bob));
let msg = get_chat_msg(&bob, bob_chat.get_id(), i.next().unwrap(), msg_cnt).await;
assert!(msg.is_info());
assert_eq!(msg.get_text(), stock_str::securejoin_wait(&bob).await);
assert_eq!(msg.get_text(), stock_str::securejoin_wait(&bob));
let contact_alice_id = bob.add_or_lookup_contact_no_key(&alice).await.id;
let sent = bob.pop_sent_msg().await;
@@ -250,7 +250,7 @@ async fn test_setup_contact_ex(case: SetupContactCase) {
let chat = alice.get_chat(&bob).await;
let msg = get_chat_msg(&alice, chat.get_id(), 0, 1).await;
assert!(msg.is_info());
let expected_text = messages_e2ee_info_msg(&alice).await;
let expected_text = messages_e2ee_info_msg(&alice);
assert_eq!(msg.get_text(), expected_text);
}
@@ -295,7 +295,7 @@ async fn test_setup_contact_ex(case: SetupContactCase) {
// The `SecurejoinWait` info message has been removed, but the e2ee notice remains.
let msg = get_chat_msg(&bob, bob_chat.get_id(), 0, 1).await;
assert!(msg.is_info());
assert_eq!(msg.get_text(), messages_e2ee_info_msg(&bob).await);
assert_eq!(msg.get_text(), messages_e2ee_info_msg(&bob));
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
@@ -625,7 +625,7 @@ async fn test_secure_join_group_ex(v3: bool, remove_invite: bool) -> Result<()>
// - You added member bob@example.net
let msg = get_chat_msg(&alice, alice_chatid, 0, 2).await;
assert!(msg.is_info());
let expected_text = messages_e2ee_info_msg(&alice).await;
let expected_text = messages_e2ee_info_msg(&alice);
assert_eq!(msg.get_text(), expected_text);
}

View File

@@ -34,12 +34,10 @@ pub(crate) fn remove_message_footer<'a>(
// some providers encode `-- ` to `=2D-` which results in only `--`;
// use that only when no other footer is found
// and if the line before is empty and the line after is not empty
"--" => {
if (ix == 0 || lines.get(ix.saturating_sub(1)).is_none_or_empty())
&& !lines.get(ix + 1).is_none_or_empty()
{
nearly_standard_footer = Some(ix);
}
"--" if (ix == 0 || lines.get(ix.saturating_sub(1)).is_none_or_empty())
&& !lines.get(ix + 1).is_none_or_empty() =>
{
nearly_standard_footer = Some(ix);
}
_ => (),
}

View File

@@ -901,7 +901,7 @@ async fn maybe_add_mvbox_move_deprecation_message(context: &Context) -> Result<(
if !context.get_config_bool(Config::OnlyFetchMvbox).await?
&& context.get_config_bool(Config::MvboxMove).await?
{
let mut msg = Message::new_text(stock_str::mvbox_move_deprecation(context).await);
let mut msg = Message::new_text(stock_str::mvbox_move_deprecation(context));
add_device_msg(context, Some("mvbox_move_deprecation"), Some(&mut msg)).await?;
}
Ok(())

View File

@@ -71,24 +71,6 @@ struct InnerPool {
/// This mutex is locked when write connection
/// is outside the pool.
pub(crate) write_mutex: Arc<Mutex<()>>,
/// WAL checkpointing mutex.
///
/// This mutex ensures that no more than one thread
/// runs WAL checkpointing at the same time.
///
/// Normal procedures acquire either one read connection
/// or one write connection with a write mutex,
/// and return the resources without trying to acquire
/// more connections or trying to acquire write mutex
/// without returning the read connection first.
/// WAL checkpointing is special, it tries to acquire all
/// connections and the write mutex,
/// so two threads doing this at the same time
/// may result in a deadlock with one thread
/// waiting for a write lock and the other thread
/// waiting for a connection.
wal_checkpoint_mutex: Mutex<()>,
}
impl InnerPool {
@@ -209,7 +191,6 @@ impl Pool {
connections: parking_lot::Mutex::new(connections),
semaphore,
write_mutex: Default::default(),
wal_checkpoint_mutex: Default::default(),
});
Pool { inner }
}

View File

@@ -34,7 +34,6 @@ pub(crate) struct WalCheckpointStats {
/// Runs a checkpoint operation in TRUNCATE mode, so the WAL file is truncated to 0 bytes.
pub(super) async fn wal_checkpoint(pool: &Pool) -> Result<WalCheckpointStats> {
let _guard = pool.inner.wal_checkpoint_mutex.lock().await;
let t_start = Time::now();
// Do as much work as possible without blocking anybody.
@@ -48,22 +47,25 @@ pub(super) async fn wal_checkpoint(pool: &Pool) -> Result<WalCheckpointStats> {
conn.query_row("PRAGMA wal_checkpoint(PASSIVE)", [], |_| Ok(()))
})?;
// Kick out writers.
const _: () = assert!(Sql::N_DB_CONNECTIONS > 1, "Deadlock possible");
// Kick out writers. `write_mutex` should be locked before taking an `InnerPool.semaphore`
// permit to avoid ABBA deadlocks, so drop `conn` which holds a semaphore permit.
drop(conn);
let _write_lock = Arc::clone(&pool.inner.write_mutex).lock_owned().await;
let t_writers_blocked = Time::now();
let conn = pool.get(query_only).await?;
// Ensure that all readers use the most recent database snapshot (are at the end of WAL) so
// that `wal_checkpoint(FULL)` isn't blocked. We could use `PASSIVE` as well, but it's
// documented poorly, https://www.sqlite.org/pragma.html#pragma_wal_checkpoint and
// https://www.sqlite.org/c3ref/wal_checkpoint_v2.html don't tell how it interacts with new
// readers.
let mut read_conns = Vec::with_capacity(crate::sql::Sql::N_DB_CONNECTIONS - 1);
for _ in 0..(crate::sql::Sql::N_DB_CONNECTIONS - 1) {
let mut read_conns = Vec::with_capacity(Sql::N_DB_CONNECTIONS - 1);
for _ in 0..(Sql::N_DB_CONNECTIONS - 1) {
read_conns.push(pool.get(query_only).await?);
}
read_conns.clear();
// Checkpoint the remaining WAL pages without blocking readers.
let (pages_total, pages_checkpointed) = tokio::task::block_in_place(|| {
conn.query_row("PRAGMA table_list", [], |_| Ok(()))?;
conn.query_row("PRAGMA wal_checkpoint(FULL)", [], |row| {
let pages_total: i64 = row.get(1)?;
let pages_checkpointed: i64 = row.get(2)?;
@@ -71,7 +73,7 @@ pub(super) async fn wal_checkpoint(pool: &Pool) -> Result<WalCheckpointStats> {
})
})?;
// Kick out readers to avoid blocking/SQLITE_BUSY.
for _ in 0..(crate::sql::Sql::N_DB_CONNECTIONS - 1) {
for _ in 0..(Sql::N_DB_CONNECTIONS - 1) {
read_conns.push(pool.get(query_only).await?);
}
let t_readers_blocked = Time::now();

View File

@@ -281,7 +281,7 @@ async fn send_stats(context: &Context) -> Result<ChatId> {
let chat_id = get_stats_chat_id(context).await?;
let mut msg = Message::new(Viewtype::File);
msg.set_text(crate::stock_str::stats_msg_body(context).await);
msg.set_text(crate::stock_str::stats_msg_body(context));
let stats = get_stats(context).await?;
@@ -554,6 +554,7 @@ async fn get_message_stats(context: &Context) -> Result<BTreeMap<Chattype, Messa
}
pub(crate) async fn update_message_stats(context: &Context) -> Result<()> {
info!(context, "Updating message statistics.");
for chattype in [Chattype::Single, Chattype::Group, Chattype::OutBroadcast] {
update_message_stats_inner(context, chattype).await?;
}

View File

@@ -4,9 +4,9 @@ use std::collections::HashMap;
use std::sync::Arc;
use anyhow::{Result, bail};
use parking_lot::RwLock;
use strum::EnumProperty as EnumPropertyTrait;
use strum_macros::EnumProperty;
use tokio::sync::RwLock;
use crate::accounts::Accounts;
use crate::blob::BlobObject;
@@ -463,17 +463,16 @@ impl StockStrings {
}
}
async fn translated(&self, id: StockMessage) -> String {
fn translated(&self, id: StockMessage) -> String {
self.translated_stockstrings
.read()
.await
.get(&(id as usize))
.map(AsRef::as_ref)
.unwrap_or_else(|| id.fallback())
.to_string()
}
async fn set_stock_translation(&self, id: StockMessage, stockstring: String) -> Result<()> {
fn set_stock_translation(&self, id: StockMessage, stockstring: String) -> Result<()> {
if stockstring.contains("%1") && !id.fallback().contains("%1") {
bail!(
"translation {} contains invalid %1 placeholder, default is {}",
@@ -490,14 +489,13 @@ impl StockStrings {
}
self.translated_stockstrings
.write()
.await
.insert(id as usize, stockstring);
Ok(())
}
}
async fn translated(context: &Context, id: StockMessage) -> String {
context.translated_stockstrings.translated(id).await
fn translated(context: &Context, id: StockMessage) -> String {
context.translated_stockstrings.translated(id)
}
/// Helper trait only meant to be implemented for [`String`].
@@ -546,43 +544,43 @@ impl ContactId {
impl StockStringMods for String {}
/// Stock string: `No messages.`.
pub(crate) async fn no_messages(context: &Context) -> String {
translated(context, StockMessage::NoMessages).await
pub(crate) fn no_messages(context: &Context) -> String {
translated(context, StockMessage::NoMessages)
}
/// Stock string: `Me`.
pub(crate) async fn self_msg(context: &Context) -> String {
translated(context, StockMessage::SelfMsg).await
pub(crate) fn self_msg(context: &Context) -> String {
translated(context, StockMessage::SelfMsg)
}
/// Stock string: `Draft`.
pub(crate) async fn draft(context: &Context) -> String {
translated(context, StockMessage::Draft).await
pub(crate) fn draft(context: &Context) -> String {
translated(context, StockMessage::Draft)
}
/// Stock string: `Voice message`.
pub(crate) async fn voice_message(context: &Context) -> String {
translated(context, StockMessage::VoiceMessage).await
pub(crate) fn voice_message(context: &Context) -> String {
translated(context, StockMessage::VoiceMessage)
}
/// Stock string: `Image`.
pub(crate) async fn image(context: &Context) -> String {
translated(context, StockMessage::Image).await
pub(crate) fn image(context: &Context) -> String {
translated(context, StockMessage::Image)
}
/// Stock string: `Video`.
pub(crate) async fn video(context: &Context) -> String {
translated(context, StockMessage::Video).await
pub(crate) fn video(context: &Context) -> String {
translated(context, StockMessage::Video)
}
/// Stock string: `Audio`.
pub(crate) async fn audio(context: &Context) -> String {
translated(context, StockMessage::Audio).await
pub(crate) fn audio(context: &Context) -> String {
translated(context, StockMessage::Audio)
}
/// Stock string: `File`.
pub(crate) async fn file(context: &Context) -> String {
translated(context, StockMessage::File).await
pub(crate) fn file(context: &Context) -> String {
translated(context, StockMessage::File)
}
/// Stock string: `Group name changed from "%1$s" to "%2$s".`.
@@ -594,12 +592,10 @@ pub(crate) async fn msg_grp_name(
) -> String {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouChangedGrpName)
.await
.replace1(from_group)
.replace2(to_group)
} else {
translated(context, StockMessage::MsgGrpNameChangedBy)
.await
.replace1(from_group)
.replace2(to_group)
.replace3(&by_contact.get_stock_name(context).await)
@@ -608,10 +604,9 @@ pub(crate) async fn msg_grp_name(
pub(crate) async fn msg_grp_img_changed(context: &Context, by_contact: ContactId) -> String {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouChangedGrpImg).await
translated(context, StockMessage::MsgYouChangedGrpImg)
} else {
translated(context, StockMessage::MsgGrpImgChangedBy)
.await
.replace1(&by_contact.get_stock_name(context).await)
}
}
@@ -621,10 +616,9 @@ pub(crate) async fn msg_chat_description_changed(
by_contact: ContactId,
) -> String {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouChangedDescription).await
translated(context, StockMessage::MsgYouChangedDescription)
} else {
translated(context, StockMessage::MsgChatDescriptionChangedBy)
.await
.replace1(&by_contact.get_stock_name(context).await)
}
}
@@ -640,16 +634,11 @@ pub(crate) async fn msg_add_member_local(
) -> String {
let whom = added_member.get_stock_name(context).await;
if by_contact == ContactId::UNDEFINED {
translated(context, StockMessage::MsgAddMember)
.await
.replace1(&whom)
translated(context, StockMessage::MsgAddMember).replace1(&whom)
} else if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouAddMember)
.await
.replace1(&whom)
translated(context, StockMessage::MsgYouAddMember).replace1(&whom)
} else {
translated(context, StockMessage::MsgAddMemberBy)
.await
.replace1(&whom)
.replace2(&by_contact.get_stock_name(context).await)
}
@@ -666,16 +655,11 @@ pub(crate) async fn msg_del_member_local(
) -> String {
let whom = removed_member.get_stock_name(context).await;
if by_contact == ContactId::UNDEFINED {
translated(context, StockMessage::MsgDelMember)
.await
.replace1(&whom)
translated(context, StockMessage::MsgDelMember).replace1(&whom)
} else if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouDelMember)
.await
.replace1(&whom)
translated(context, StockMessage::MsgYouDelMember).replace1(&whom)
} else {
translated(context, StockMessage::MsgDelMemberBy)
.await
.replace1(&whom)
.replace2(&by_contact.get_stock_name(context).await)
}
@@ -684,22 +668,21 @@ pub(crate) async fn msg_del_member_local(
/// Stock string: `You left the group.` or `Group left by %1$s.`.
pub(crate) async fn msg_group_left_local(context: &Context, by_contact: ContactId) -> String {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouLeftGroup).await
translated(context, StockMessage::MsgYouLeftGroup)
} else {
translated(context, StockMessage::MsgGroupLeftBy)
.await
.replace1(&by_contact.get_stock_name(context).await)
}
}
/// Stock string: `You left the channel.`
pub(crate) async fn msg_you_left_broadcast(context: &Context) -> String {
translated(context, StockMessage::MsgYouLeftBroadcast).await
pub(crate) fn msg_you_left_broadcast(context: &Context) -> String {
translated(context, StockMessage::MsgYouLeftBroadcast)
}
/// Stock string: `You joined the channel.`
pub(crate) async fn msg_you_joined_broadcast(context: &Context) -> String {
translated(context, StockMessage::MsgYouJoinedBroadcast).await
pub(crate) fn msg_you_joined_broadcast(context: &Context) -> String {
translated(context, StockMessage::MsgYouJoinedBroadcast)
}
/// Stock string: `%1$s invited you to join this channel. Waiting for the device of %2$s to reply…`.
@@ -709,7 +692,6 @@ pub(crate) async fn secure_join_broadcast_started(
) -> String {
if let Ok(contact) = Contact::get_by_id(context, inviter_contact_id).await {
translated(context, StockMessage::SecureJoinBroadcastStarted)
.await
.replace1(contact.get_display_name())
.replace2(contact.get_display_name())
} else {
@@ -718,16 +700,15 @@ pub(crate) async fn secure_join_broadcast_started(
}
/// Stock string: `Channel name changed from "1%s" to "2$s".`
pub(crate) async fn msg_broadcast_name_changed(context: &Context, from: &str, to: &str) -> String {
pub(crate) fn msg_broadcast_name_changed(context: &Context, from: &str, to: &str) -> String {
translated(context, StockMessage::MsgBroadcastNameChanged)
.await
.replace1(from)
.replace2(to)
}
/// Stock string `Channel image changed.`
pub(crate) async fn msg_broadcast_img_changed(context: &Context) -> String {
translated(context, StockMessage::MsgBroadcastImgChanged).await
pub(crate) fn msg_broadcast_img_changed(context: &Context) -> String {
translated(context, StockMessage::MsgBroadcastImgChanged)
}
/// Stock string: `You reacted %1$s to "%2$s"` or `%1$s reacted %2$s to "%3$s"`.
@@ -739,12 +720,10 @@ pub(crate) async fn msg_reacted(
) -> String {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouReacted)
.await
.replace1(reaction)
.replace2(summary)
} else {
translated(context, StockMessage::MsgReactedBy)
.await
.replace1(&by_contact.get_stock_name(context).await)
.replace2(reaction)
.replace3(summary)
@@ -752,27 +731,26 @@ pub(crate) async fn msg_reacted(
}
/// Stock string: `GIF`.
pub(crate) async fn gif(context: &Context) -> String {
translated(context, StockMessage::Gif).await
pub(crate) fn gif(context: &Context) -> String {
translated(context, StockMessage::Gif)
}
/// Stock string: `No encryption.`.
pub(crate) async fn encr_none(context: &Context) -> String {
translated(context, StockMessage::EncrNone).await
pub(crate) fn encr_none(context: &Context) -> String {
translated(context, StockMessage::EncrNone)
}
/// Stock string: `Fingerprints`.
pub(crate) async fn finger_prints(context: &Context) -> String {
translated(context, StockMessage::FingerPrints).await
pub(crate) fn finger_prints(context: &Context) -> String {
translated(context, StockMessage::FingerPrints)
}
/// Stock string: `Group image deleted.`.
pub(crate) async fn msg_grp_img_deleted(context: &Context, by_contact: ContactId) -> String {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouDeletedGrpImg).await
translated(context, StockMessage::MsgYouDeletedGrpImg)
} else {
translated(context, StockMessage::MsgGrpImgDeletedBy)
.await
.replace1(&by_contact.get_stock_name(context).await)
}
}
@@ -784,7 +762,6 @@ pub(crate) async fn secure_join_started(
) -> String {
if let Ok(contact) = Contact::get_by_id(context, inviter_contact_id).await {
translated(context, StockMessage::SecureJoinStarted)
.await
.replace1(contact.get_display_name())
.replace2(contact.get_display_name())
} else {
@@ -795,22 +772,21 @@ pub(crate) async fn secure_join_started(
/// Stock string: `%1$s replied, waiting for being added to the group…`.
pub(crate) async fn secure_join_replies(context: &Context, contact_id: ContactId) -> String {
translated(context, StockMessage::SecureJoinReplies)
.await
.replace1(&contact_id.get_stock_name(context).await)
}
/// Stock string: `Establishing connection, please wait…`.
pub(crate) async fn securejoin_wait(context: &Context) -> String {
translated(context, StockMessage::SecurejoinWait).await
pub(crate) fn securejoin_wait(context: &Context) -> String {
translated(context, StockMessage::SecurejoinWait)
}
/// Stock string: `❤️ Seems you're enjoying Delta Chat!`…
pub(crate) async fn donation_request(context: &Context) -> String {
translated(context, StockMessage::DonationRequest).await
pub(crate) fn donation_request(context: &Context) -> String {
translated(context, StockMessage::DonationRequest)
}
/// Stock string: `Outgoing video call` or `Outgoing audio call`.
pub(crate) async fn outgoing_call(context: &Context, has_video: bool) -> String {
pub(crate) fn outgoing_call(context: &Context, has_video: bool) -> String {
translated(
context,
if has_video {
@@ -819,11 +795,10 @@ pub(crate) async fn outgoing_call(context: &Context, has_video: bool) -> String
StockMessage::OutgoingAudioCall
},
)
.await
}
/// Stock string: `Incoming video call` or `Incoming audio call`.
pub(crate) async fn incoming_call(context: &Context, has_video: bool) -> String {
pub(crate) fn incoming_call(context: &Context, has_video: bool) -> String {
translated(
context,
if has_video {
@@ -832,26 +807,25 @@ pub(crate) async fn incoming_call(context: &Context, has_video: bool) -> String
StockMessage::IncomingAudioCall
},
)
.await
}
/// Stock string: `Declined call`.
pub(crate) async fn declined_call(context: &Context) -> String {
translated(context, StockMessage::DeclinedCall).await
pub(crate) fn declined_call(context: &Context) -> String {
translated(context, StockMessage::DeclinedCall)
}
/// Stock string: `Canceled call`.
pub(crate) async fn canceled_call(context: &Context) -> String {
translated(context, StockMessage::CanceledCall).await
pub(crate) fn canceled_call(context: &Context) -> String {
translated(context, StockMessage::CanceledCall)
}
/// Stock string: `Missed call`.
pub(crate) async fn missed_call(context: &Context) -> String {
translated(context, StockMessage::MissedCall).await
pub(crate) fn missed_call(context: &Context) -> String {
translated(context, StockMessage::MissedCall)
}
/// Stock string: `Scan to chat with %1$s`.
pub(crate) async fn setup_contact_qr_description(
pub(crate) fn setup_contact_qr_description(
context: &Context,
display_name: &str,
addr: &str,
@@ -861,113 +835,100 @@ pub(crate) async fn setup_contact_qr_description(
} else {
display_name.to_owned()
};
translated(context, StockMessage::SetupContactQRDescription)
.await
.replace1(&name)
translated(context, StockMessage::SetupContactQRDescription).replace1(&name)
}
/// Stock string: `Scan to join group %1$s`.
pub(crate) async fn secure_join_group_qr_description(context: &Context, chat: &Chat) -> String {
translated(context, StockMessage::SecureJoinGroupQRDescription)
.await
.replace1(chat.get_name())
pub(crate) fn secure_join_group_qr_description(context: &Context, chat: &Chat) -> String {
translated(context, StockMessage::SecureJoinGroupQRDescription).replace1(chat.get_name())
}
/// Stock string: `Scan to join channel %1$s`.
pub(crate) async fn secure_join_broadcast_qr_description(context: &Context, chat: &Chat) -> String {
translated(context, StockMessage::SecureJoinBrodcastQRDescription)
.await
.replace1(chat.get_name())
pub(crate) fn secure_join_broadcast_qr_description(context: &Context, chat: &Chat) -> String {
translated(context, StockMessage::SecureJoinBrodcastQRDescription).replace1(chat.get_name())
}
/// Stock string: `%1$s verified.`.
#[allow(dead_code)]
pub(crate) async fn contact_verified(context: &Context, contact: &Contact) -> String {
pub(crate) fn contact_verified(context: &Context, contact: &Contact) -> String {
let addr = contact.get_display_name();
translated(context, StockMessage::ContactVerified)
.await
.replace1(addr)
translated(context, StockMessage::ContactVerified).replace1(addr)
}
/// Stock string: `Archived chats`.
pub(crate) async fn archived_chats(context: &Context) -> String {
translated(context, StockMessage::ArchivedChats).await
pub(crate) fn archived_chats(context: &Context) -> String {
translated(context, StockMessage::ArchivedChats)
}
/// Stock string: `Multi Device Synchronization`.
pub(crate) async fn sync_msg_subject(context: &Context) -> String {
translated(context, StockMessage::SyncMsgSubject).await
pub(crate) fn sync_msg_subject(context: &Context) -> String {
translated(context, StockMessage::SyncMsgSubject)
}
/// Stock string: `This message is used to synchronize data between your devices.`.
pub(crate) async fn sync_msg_body(context: &Context) -> String {
translated(context, StockMessage::SyncMsgBody).await
pub(crate) fn sync_msg_body(context: &Context) -> String {
translated(context, StockMessage::SyncMsgBody)
}
/// Stock string: `Cannot login as \"%1$s\". Please check...`.
pub(crate) async fn cannot_login(context: &Context, user: &str) -> String {
translated(context, StockMessage::CannotLogin)
.await
.replace1(user)
pub(crate) fn cannot_login(context: &Context, user: &str) -> String {
translated(context, StockMessage::CannotLogin).replace1(user)
}
/// Stock string: `Location streaming enabled.`.
pub(crate) async fn msg_location_enabled(context: &Context) -> String {
translated(context, StockMessage::MsgLocationEnabled).await
pub(crate) fn msg_location_enabled(context: &Context) -> String {
translated(context, StockMessage::MsgLocationEnabled)
}
/// Stock string: `Location streaming enabled by ...`.
pub(crate) async fn msg_location_enabled_by(context: &Context, contact: ContactId) -> String {
if contact == ContactId::SELF {
translated(context, StockMessage::MsgYouEnabledLocation).await
translated(context, StockMessage::MsgYouEnabledLocation)
} else {
translated(context, StockMessage::MsgLocationEnabledBy)
.await
.replace1(&contact.get_stock_name(context).await)
}
}
/// Stock string: `Location streaming disabled.`.
pub(crate) async fn msg_location_disabled(context: &Context) -> String {
translated(context, StockMessage::MsgLocationDisabled).await
pub(crate) fn msg_location_disabled(context: &Context) -> String {
translated(context, StockMessage::MsgLocationDisabled)
}
/// Stock string: `Location`.
pub(crate) async fn location(context: &Context) -> String {
translated(context, StockMessage::Location).await
pub(crate) fn location(context: &Context) -> String {
translated(context, StockMessage::Location)
}
/// Stock string: `Sticker`.
pub(crate) async fn sticker(context: &Context) -> String {
translated(context, StockMessage::Sticker).await
pub(crate) fn sticker(context: &Context) -> String {
translated(context, StockMessage::Sticker)
}
/// Stock string: `Device messages`.
pub(crate) async fn device_messages(context: &Context) -> String {
translated(context, StockMessage::DeviceMessages).await
pub(crate) fn device_messages(context: &Context) -> String {
translated(context, StockMessage::DeviceMessages)
}
/// Stock string: `Saved messages`.
pub(crate) async fn saved_messages(context: &Context) -> String {
translated(context, StockMessage::SavedMessages).await
pub(crate) fn saved_messages(context: &Context) -> String {
translated(context, StockMessage::SavedMessages)
}
/// Stock string: `Messages in this chat are generated locally by...`.
pub(crate) async fn device_messages_hint(context: &Context) -> String {
translated(context, StockMessage::DeviceMessagesHint).await
pub(crate) fn device_messages_hint(context: &Context) -> String {
translated(context, StockMessage::DeviceMessagesHint)
}
/// Stock string: `Welcome to Delta Chat! ...`.
pub(crate) async fn welcome_message(context: &Context) -> String {
translated(context, StockMessage::WelcomeMessage).await
pub(crate) fn welcome_message(context: &Context) -> String {
translated(context, StockMessage::WelcomeMessage)
}
/// Stock string: `Message from %1$s`.
// TODO: This can compute `self_name` itself instead of asking the caller to do this.
pub(crate) async fn subject_for_new_contact(context: &Context, self_name: &str) -> String {
translated(context, StockMessage::SubjectForNewContact)
.await
.replace1(self_name)
pub(crate) fn subject_for_new_contact(context: &Context, self_name: &str) -> String {
translated(context, StockMessage::SubjectForNewContact).replace1(self_name)
}
/// Stock string: `Message deletion timer is disabled.`.
@@ -976,10 +937,9 @@ pub(crate) async fn msg_ephemeral_timer_disabled(
by_contact: ContactId,
) -> String {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouDisabledEphemeralTimer).await
translated(context, StockMessage::MsgYouDisabledEphemeralTimer)
} else {
translated(context, StockMessage::MsgEphemeralTimerDisabledBy)
.await
.replace1(&by_contact.get_stock_name(context).await)
}
}
@@ -991,12 +951,9 @@ pub(crate) async fn msg_ephemeral_timer_enabled(
by_contact: ContactId,
) -> String {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouEnabledEphemeralTimer)
.await
.replace1(timer)
translated(context, StockMessage::MsgYouEnabledEphemeralTimer).replace1(timer)
} else {
translated(context, StockMessage::MsgEphemeralTimerEnabledBy)
.await
.replace1(timer)
.replace2(&by_contact.get_stock_name(context).await)
}
@@ -1005,10 +962,9 @@ pub(crate) async fn msg_ephemeral_timer_enabled(
/// Stock string: `Message deletion timer is set to 1 hour.`.
pub(crate) async fn msg_ephemeral_timer_hour(context: &Context, by_contact: ContactId) -> String {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouEphemeralTimerHour).await
translated(context, StockMessage::MsgYouEphemeralTimerHour)
} else {
translated(context, StockMessage::MsgEphemeralTimerHourBy)
.await
.replace1(&by_contact.get_stock_name(context).await)
}
}
@@ -1016,10 +972,9 @@ pub(crate) async fn msg_ephemeral_timer_hour(context: &Context, by_contact: Cont
/// Stock string: `Message deletion timer is set to 1 day.`.
pub(crate) async fn msg_ephemeral_timer_day(context: &Context, by_contact: ContactId) -> String {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouEphemeralTimerDay).await
translated(context, StockMessage::MsgYouEphemeralTimerDay)
} else {
translated(context, StockMessage::MsgEphemeralTimerDayBy)
.await
.replace1(&by_contact.get_stock_name(context).await)
}
}
@@ -1027,10 +982,9 @@ pub(crate) async fn msg_ephemeral_timer_day(context: &Context, by_contact: Conta
/// Stock string: `Message deletion timer is set to 1 week.`.
pub(crate) async fn msg_ephemeral_timer_week(context: &Context, by_contact: ContactId) -> String {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouEphemeralTimerWeek).await
translated(context, StockMessage::MsgYouEphemeralTimerWeek)
} else {
translated(context, StockMessage::MsgEphemeralTimerWeekBy)
.await
.replace1(&by_contact.get_stock_name(context).await)
}
}
@@ -1038,57 +992,52 @@ pub(crate) async fn msg_ephemeral_timer_week(context: &Context, by_contact: Cont
/// Stock string: `Message deletion timer is set to 1 year.`.
pub(crate) async fn msg_ephemeral_timer_year(context: &Context, by_contact: ContactId) -> String {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouEphemeralTimerYear).await
translated(context, StockMessage::MsgYouEphemeralTimerYear)
} else {
translated(context, StockMessage::MsgEphemeralTimerYearBy)
.await
.replace1(&by_contact.get_stock_name(context).await)
}
}
/// Stock string: `Error:\n\n“%1$s”`.
pub(crate) async fn configuration_failed(context: &Context, details: &str) -> String {
translated(context, StockMessage::ConfigurationFailed)
.await
.replace1(details)
pub(crate) fn configuration_failed(context: &Context, details: &str) -> String {
translated(context, StockMessage::ConfigurationFailed).replace1(details)
}
/// Stock string: `⚠️ Date or time of your device seem to be inaccurate (%1$s)...`.
// TODO: This could compute now itself.
pub(crate) async fn bad_time_msg_body(context: &Context, now: &str) -> String {
translated(context, StockMessage::BadTimeMsgBody)
.await
.replace1(now)
pub(crate) fn bad_time_msg_body(context: &Context, now: &str) -> String {
translated(context, StockMessage::BadTimeMsgBody).replace1(now)
}
/// Stock string: `⚠️ Your Delta Chat version might be outdated...`.
pub(crate) async fn update_reminder_msg_body(context: &Context) -> String {
translated(context, StockMessage::UpdateReminderMsgBody).await
pub(crate) fn update_reminder_msg_body(context: &Context) -> String {
translated(context, StockMessage::UpdateReminderMsgBody)
}
/// Stock string: `Could not find your mail server...`.
pub(crate) async fn error_no_network(context: &Context) -> String {
translated(context, StockMessage::ErrorNoNetwork).await
pub(crate) fn error_no_network(context: &Context) -> String {
translated(context, StockMessage::ErrorNoNetwork)
}
/// Stock string: `Messages are end-to-end encrypted.`, used in info-messages, UI may add smth. as `Tap to learn more.`
pub(crate) async fn messages_e2ee_info_msg(context: &Context) -> String {
translated(context, StockMessage::ChatProtectionEnabled).await
pub(crate) fn messages_e2ee_info_msg(context: &Context) -> String {
translated(context, StockMessage::ChatProtectionEnabled)
}
/// Stock string: `Messages are end-to-end encrypted.`
pub(crate) async fn messages_are_e2ee(context: &Context) -> String {
translated(context, StockMessage::MessagesAreE2ee).await
pub(crate) fn messages_are_e2ee(context: &Context) -> String {
translated(context, StockMessage::MessagesAreE2ee)
}
/// Stock string: `Reply`.
pub(crate) async fn reply_noun(context: &Context) -> String {
translated(context, StockMessage::ReplyNoun).await
pub(crate) fn reply_noun(context: &Context) -> String {
translated(context, StockMessage::ReplyNoun)
}
/// Stock string: `You deleted the \"Saved messages\" chat...`.
pub(crate) async fn self_deleted_msg_body(context: &Context) -> String {
translated(context, StockMessage::SelfDeletedMsgBody).await
pub(crate) fn self_deleted_msg_body(context: &Context) -> String {
translated(context, StockMessage::SelfDeletedMsgBody)
}
/// Stock string: `Message deletion timer is set to %1$s minutes.`.
@@ -1098,12 +1047,9 @@ pub(crate) async fn msg_ephemeral_timer_minutes(
by_contact: ContactId,
) -> String {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouEphemeralTimerMinutes)
.await
.replace1(minutes)
translated(context, StockMessage::MsgYouEphemeralTimerMinutes).replace1(minutes)
} else {
translated(context, StockMessage::MsgEphemeralTimerMinutesBy)
.await
.replace1(minutes)
.replace2(&by_contact.get_stock_name(context).await)
}
@@ -1116,12 +1062,9 @@ pub(crate) async fn msg_ephemeral_timer_hours(
by_contact: ContactId,
) -> String {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouEphemeralTimerHours)
.await
.replace1(hours)
translated(context, StockMessage::MsgYouEphemeralTimerHours).replace1(hours)
} else {
translated(context, StockMessage::MsgEphemeralTimerHoursBy)
.await
.replace1(hours)
.replace2(&by_contact.get_stock_name(context).await)
}
@@ -1134,12 +1077,9 @@ pub(crate) async fn msg_ephemeral_timer_days(
by_contact: ContactId,
) -> String {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouEphemeralTimerDays)
.await
.replace1(days)
translated(context, StockMessage::MsgYouEphemeralTimerDays).replace1(days)
} else {
translated(context, StockMessage::MsgEphemeralTimerDaysBy)
.await
.replace1(days)
.replace2(&by_contact.get_stock_name(context).await)
}
@@ -1152,112 +1092,103 @@ pub(crate) async fn msg_ephemeral_timer_weeks(
by_contact: ContactId,
) -> String {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouEphemeralTimerWeeks)
.await
.replace1(weeks)
translated(context, StockMessage::MsgYouEphemeralTimerWeeks).replace1(weeks)
} else {
translated(context, StockMessage::MsgEphemeralTimerWeeksBy)
.await
.replace1(weeks)
.replace2(&by_contact.get_stock_name(context).await)
}
}
/// Stock string: `Forwarded`.
pub(crate) async fn forwarded(context: &Context) -> String {
translated(context, StockMessage::Forwarded).await
pub(crate) fn forwarded(context: &Context) -> String {
translated(context, StockMessage::Forwarded)
}
/// Stock string: `⚠️ Your provider's storage is about to exceed...`.
pub(crate) async fn quota_exceeding(context: &Context, highest_usage: u64) -> String {
pub(crate) fn quota_exceeding(context: &Context, highest_usage: u64) -> String {
translated(context, StockMessage::QuotaExceedingMsgBody)
.await
.replace1(&format!("{highest_usage}"))
.replace("%%", "%")
}
/// Stock string: `Incoming Messages`.
pub(crate) async fn incoming_messages(context: &Context) -> String {
translated(context, StockMessage::IncomingMessages).await
pub(crate) fn incoming_messages(context: &Context) -> String {
translated(context, StockMessage::IncomingMessages)
}
/// Stock string: `Outgoing Messages`.
pub(crate) async fn outgoing_messages(context: &Context) -> String {
translated(context, StockMessage::OutgoingMessages).await
pub(crate) fn outgoing_messages(context: &Context) -> String {
translated(context, StockMessage::OutgoingMessages)
}
/// Stock string: `Not connected`.
pub(crate) async fn not_connected(context: &Context) -> String {
translated(context, StockMessage::NotConnected).await
pub(crate) fn not_connected(context: &Context) -> String {
translated(context, StockMessage::NotConnected)
}
/// Stock string: `Connected`.
pub(crate) async fn connected(context: &Context) -> String {
translated(context, StockMessage::Connected).await
pub(crate) fn connected(context: &Context) -> String {
translated(context, StockMessage::Connected)
}
/// Stock string: `Connecting…`.
pub(crate) async fn connecting(context: &Context) -> String {
translated(context, StockMessage::Connecting).await
pub(crate) fn connecting(context: &Context) -> String {
translated(context, StockMessage::Connecting)
}
/// Stock string: `Updating…`.
pub(crate) async fn updating(context: &Context) -> String {
translated(context, StockMessage::Updating).await
pub(crate) fn updating(context: &Context) -> String {
translated(context, StockMessage::Updating)
}
/// Stock string: `Sending…`.
pub(crate) async fn sending(context: &Context) -> String {
translated(context, StockMessage::Sending).await
pub(crate) fn sending(context: &Context) -> String {
translated(context, StockMessage::Sending)
}
/// Stock string: `Your last message was sent successfully.`.
pub(crate) async fn last_msg_sent_successfully(context: &Context) -> String {
translated(context, StockMessage::LastMsgSentSuccessfully).await
pub(crate) fn last_msg_sent_successfully(context: &Context) -> String {
translated(context, StockMessage::LastMsgSentSuccessfully)
}
/// Stock string: `Error: %1$s…`.
/// `%1$s` will be replaced by a possibly more detailed, typically english, error description.
pub(crate) async fn error(context: &Context, error: &str) -> String {
translated(context, StockMessage::Error)
.await
.replace1(error)
pub(crate) fn error(context: &Context, error: &str) -> String {
translated(context, StockMessage::Error).replace1(error)
}
/// Stock string: `Not supported by your provider.`.
pub(crate) async fn not_supported_by_provider(context: &Context) -> String {
translated(context, StockMessage::NotSupportedByProvider).await
pub(crate) fn not_supported_by_provider(context: &Context) -> String {
translated(context, StockMessage::NotSupportedByProvider)
}
/// Stock string: `Messages`.
/// Used as a subtitle in quota context; can be plural always.
pub(crate) async fn messages(context: &Context) -> String {
translated(context, StockMessage::Messages).await
pub(crate) fn messages(context: &Context) -> String {
translated(context, StockMessage::Messages)
}
/// Stock string: `%1$s of %2$s used`.
pub(crate) async fn part_of_total_used(context: &Context, part: &str, total: &str) -> String {
pub(crate) fn part_of_total_used(context: &Context, part: &str, total: &str) -> String {
translated(context, StockMessage::PartOfTotallUsed)
.await
.replace1(part)
.replace2(total)
}
/// Stock string: `⚠️ Your email provider %1$s requires end-to-end encryption which is not setup yet. Tap to learn more.`.
pub(crate) async fn unencrypted_email(context: &Context, provider: &str) -> String {
translated(context, StockMessage::InvalidUnencryptedMail)
.await
.replace1(provider)
translated(context, StockMessage::InvalidUnencryptedMail).replace1(provider)
}
/// Stock string: `The attachment contains anonymous usage statistics, which helps us improve Delta Chat. Thank you!`
pub(crate) async fn stats_msg_body(context: &Context) -> String {
translated(context, StockMessage::StatsMsgBody).await
pub(crate) fn stats_msg_body(context: &Context) -> String {
translated(context, StockMessage::StatsMsgBody)
}
/// Stock string: `Others will only see this group after you sent a first message.`.
pub(crate) async fn new_group_send_first_message(context: &Context) -> String {
translated(context, StockMessage::NewGroupSendFirstMessage).await
pub(crate) fn new_group_send_first_message(context: &Context) -> String {
translated(context, StockMessage::NewGroupSendFirstMessage)
}
/// Text to put in the [`Qr::Backup2`] rendered SVG image.
@@ -1272,46 +1203,44 @@ pub(crate) async fn backup_transfer_qr(context: &Context) -> Result<String> {
} else {
context.get_primary_self_addr().await?
};
Ok(translated(context, StockMessage::BackupTransferQr)
.await
.replace1(&name))
Ok(translated(context, StockMessage::BackupTransferQr).replace1(&name))
}
pub(crate) async fn backup_transfer_msg_body(context: &Context) -> String {
translated(context, StockMessage::BackupTransferMsgBody).await
pub(crate) fn backup_transfer_msg_body(context: &Context) -> String {
translated(context, StockMessage::BackupTransferMsgBody)
}
/// Stock string: `Proxy Enabled`.
pub(crate) async fn proxy_enabled(context: &Context) -> String {
translated(context, StockMessage::ProxyEnabled).await
pub(crate) fn proxy_enabled(context: &Context) -> String {
translated(context, StockMessage::ProxyEnabled)
}
/// Stock string: `You are using a proxy. If you're having trouble connecting, try a different proxy.`.
pub(crate) async fn proxy_description(context: &Context) -> String {
translated(context, StockMessage::ProxyEnabledDescription).await
pub(crate) fn proxy_description(context: &Context) -> String {
translated(context, StockMessage::ProxyEnabledDescription)
}
/// Stock string: `Messages in this chat use classic email and are not encrypted.`.
pub(crate) async fn chat_unencrypted_explanation(context: &Context) -> String {
translated(context, StockMessage::ChatUnencryptedExplanation).await
pub(crate) fn chat_unencrypted_explanation(context: &Context) -> String {
translated(context, StockMessage::ChatUnencryptedExplanation)
}
/// Stock string: `You are using the legacy option "Move automatically to DeltaChat Folder`…
pub(crate) async fn mvbox_move_deprecation(context: &Context) -> String {
translated(context, StockMessage::MvboxMoveDeprecation).await
pub(crate) fn mvbox_move_deprecation(context: &Context) -> String {
translated(context, StockMessage::MvboxMoveDeprecation)
}
impl Viewtype {
/// returns Localized name for message viewtype
pub async fn to_locale_string(&self, context: &Context) -> String {
pub fn to_locale_string(&self, context: &Context) -> String {
match self {
Viewtype::Image => image(context).await,
Viewtype::Gif => gif(context).await,
Viewtype::Sticker => sticker(context).await,
Viewtype::Audio => audio(context).await,
Viewtype::Voice => voice_message(context).await,
Viewtype::Video => video(context).await,
Viewtype::File => file(context).await,
Viewtype::Image => image(context),
Viewtype::Gif => gif(context),
Viewtype::Sticker => sticker(context),
Viewtype::Audio => audio(context),
Viewtype::Voice => voice_message(context),
Viewtype::Video => video(context),
Viewtype::File => file(context),
Viewtype::Webxdc => "Mini App".to_owned(),
Viewtype::Vcard => "👤".to_string(),
// The following shouldn't normally be shown to users, so translations aren't needed.
@@ -1323,10 +1252,9 @@ impl Viewtype {
impl Context {
/// Set the stock string for the [StockMessage].
///
pub async fn set_stock_translation(&self, id: StockMessage, stockstring: String) -> Result<()> {
pub fn set_stock_translation(&self, id: StockMessage, stockstring: String) -> Result<()> {
self.translated_stockstrings
.set_stock_translation(id, stockstring)
.await?;
.set_stock_translation(id, stockstring)?;
Ok(())
}
@@ -1353,7 +1281,7 @@ impl Context {
msg.param.set(Param::Filename, "welcome-image.jpg");
chat::add_device_msg(self, Some("core-welcome-image"), Some(&mut msg)).await?;
let mut msg = Message::new_text(welcome_message(self).await);
let mut msg = Message::new_text(welcome_message(self));
chat::add_device_msg(self, Some("core-welcome"), Some(&mut msg)).await?;
Ok(())
}
@@ -1362,10 +1290,8 @@ impl Context {
impl Accounts {
/// Set the stock string for the [StockMessage].
///
pub async fn set_stock_translation(&self, id: StockMessage, stockstring: String) -> Result<()> {
self.stockstrings
.set_stock_translation(id, stockstring)
.await?;
pub fn set_stock_translation(&self, id: StockMessage, stockstring: String) -> Result<()> {
self.stockstrings.set_stock_translation(id, stockstring)?;
Ok(())
}
}

View File

@@ -20,9 +20,8 @@ fn test_fallback() {
async fn test_set_stock_translation() {
let t = TestContext::new().await;
t.set_stock_translation(StockMessage::NoMessages, "xyz".to_string())
.await
.unwrap();
assert_eq!(no_messages(&t).await, "xyz")
assert_eq!(no_messages(&t), "xyz")
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
@@ -31,13 +30,11 @@ async fn test_set_stock_translation_wrong_replacements() {
assert!(
t.ctx
.set_stock_translation(StockMessage::NoMessages, "xyz %1$s ".to_string())
.await
.is_err()
);
assert!(
t.ctx
.set_stock_translation(StockMessage::NoMessages, "xyz %2$s ".to_string())
.await
.is_err()
);
}
@@ -45,7 +42,7 @@ async fn test_set_stock_translation_wrong_replacements() {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_stock_str() {
let t = TestContext::new().await;
assert_eq!(no_messages(&t).await, "No messages.");
assert_eq!(no_messages(&t), "No messages.");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
@@ -56,17 +53,14 @@ async fn test_stock_string_repl_str() {
.unwrap();
let contact = Contact::get_by_id(&t.ctx, contact_id).await.unwrap();
// uses %1$s substitution
assert_eq!(contact_verified(&t, &contact).await, "Someone verified.");
assert_eq!(contact_verified(&t, &contact), "Someone verified.");
// We have no string using %1$d to test...
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_stock_system_msg_simple() {
let t = TestContext::new().await;
assert_eq!(
msg_location_enabled(&t).await,
"Location streaming enabled."
)
assert_eq!(msg_location_enabled(&t), "Location streaming enabled.")
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
@@ -111,7 +105,7 @@ 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).await;
let str = quota_exceeding(&t, 81);
assert!(str.contains("81% "));
assert!(str.contains("100% "));
assert!(!str.contains("%%"));

View File

@@ -98,13 +98,13 @@ impl Summary {
contact: Option<&Contact>,
) -> Result<Summary> {
let prefix = if msg.state == MessageState::OutDraft {
Some(SummaryPrefix::Draft(stock_str::draft(context).await))
Some(SummaryPrefix::Draft(stock_str::draft(context)))
} else if msg.from_id == ContactId::SELF {
if msg.is_info() || msg.viewtype == Viewtype::Call || chat.typ == Chattype::OutBroadcast
{
None
} else {
Some(SummaryPrefix::Me(stock_str::self_msg(context).await))
Some(SummaryPrefix::Me(stock_str::self_msg(context)))
}
} else if chat.typ == Chattype::Group
|| chat.typ == Chattype::Mailinglist
@@ -124,7 +124,7 @@ impl Summary {
let mut text = msg.get_summary_text(context).await;
if text.is_empty() && msg.quoted_text().is_some() {
text = stock_str::reply_noun(context).await
text = stock_str::reply_noun(context)
}
let thumbnail_path = if msg.viewtype == Viewtype::Image
@@ -160,7 +160,7 @@ impl Message {
let summary = self.get_summary_text_without_prefix(context).await;
if self.is_forwarded() {
format!("{}: {}", stock_str::forwarded(context).await, summary)
format!("{}: {}", stock_str::forwarded(context), summary)
} else {
summary
}
@@ -180,43 +180,43 @@ impl Message {
match viewtype {
Viewtype::Image => {
emoji = Some("📷");
type_name = Some(stock_str::image(context).await);
type_name = Some(stock_str::image(context));
type_file = None;
append_text = true;
}
Viewtype::Gif => {
emoji = None;
type_name = Some(stock_str::gif(context).await);
type_name = Some(stock_str::gif(context));
type_file = None;
append_text = true;
}
Viewtype::Sticker => {
emoji = None;
type_name = Some(stock_str::sticker(context).await);
type_name = Some(stock_str::sticker(context));
type_file = None;
append_text = true;
}
Viewtype::Video => {
emoji = Some("🎥");
type_name = Some(stock_str::video(context).await);
type_name = Some(stock_str::video(context));
type_file = None;
append_text = true;
}
Viewtype::Voice => {
emoji = Some("🎤");
type_name = Some(stock_str::voice_message(context).await);
type_name = Some(stock_str::voice_message(context));
type_file = None;
append_text = true;
}
Viewtype::Audio => {
emoji = Some("🎵");
type_name = Some(stock_str::audio(context).await);
type_name = Some(stock_str::audio(context));
type_file = self.get_filename();
append_text = true
}
Viewtype::File => {
emoji = Some("📎");
type_name = Some(stock_str::file(context).await);
type_name = Some(stock_str::file(context));
type_file = self.get_filename();
append_text = true
}
@@ -255,14 +255,14 @@ impl Message {
type_name = Some(match call_state {
CallState::Alerting | CallState::Active | CallState::Completed { .. } => {
if self.from_id == ContactId::SELF {
stock_str::outgoing_call(context, has_video).await
stock_str::outgoing_call(context, has_video)
} else {
stock_str::incoming_call(context, has_video).await
stock_str::incoming_call(context, has_video)
}
}
CallState::Missed => stock_str::missed_call(context).await,
CallState::Declined => stock_str::declined_call(context).await,
CallState::Canceled => stock_str::canceled_call(context).await,
CallState::Missed => stock_str::missed_call(context),
CallState::Declined => stock_str::declined_call(context),
CallState::Canceled => stock_str::canceled_call(context),
});
type_file = None;
append_text = false
@@ -270,7 +270,7 @@ impl Message {
Viewtype::Text | Viewtype::Unknown => {
emoji = None;
if self.param.get_cmd() == SystemMessage::LocationOnly {
type_name = Some(stock_str::location(context).await);
type_name = Some(stock_str::location(context));
type_file = None;
append_text = false;
} else {

View File

@@ -229,9 +229,9 @@ impl Context {
let mut msg = Message {
chat_id,
viewtype: Viewtype::Text,
text: stock_str::sync_msg_body(self).await,
text: stock_str::sync_msg_body(self),
hidden: true,
subject: stock_str::sync_msg_subject(self).await,
subject: stock_str::sync_msg_subject(self),
..Default::default()
};
msg.param.set_cmd(SystemMessage::MultiDeviceSync);

View File

@@ -195,7 +195,7 @@ async fn test_degrade_verified_oneonone_chat() -> Result<()> {
.await?;
let msg0 = get_chat_msg(&alice, alice_chat.id, 0, 1).await;
let enabled = stock_str::messages_e2ee_info_msg(&alice).await;
let enabled = stock_str::messages_e2ee_info_msg(&alice);
assert_eq!(msg0.text, enabled);
assert_eq!(msg0.param.get_cmd(), SystemMessage::ChatE2ee);

View File

@@ -21,6 +21,7 @@ pub use std::time::SystemTime as Time;
#[cfg(not(test))]
pub use std::time::SystemTime;
use crate::log::LogExt as _;
use anyhow::{Context as _, Result, bail, ensure};
use base64::Engine as _;
use chrono::{Local, NaiveDateTime, NaiveTime, TimeZone};
@@ -233,8 +234,7 @@ async fn maybe_warn_on_bad_time(context: &Context, now: i64, known_past_timestam
|| "YY-MM-DD hh:mm:ss".to_string(),
|ts| ts.format("%Y-%m-%d %H:%M:%S").to_string(),
),
)
.await;
);
if let Some(timestamp) = chrono::DateTime::<chrono::Utc>::from_timestamp(now, 0) {
add_device_msg_with_importance(
context,
@@ -249,6 +249,8 @@ async fn maybe_warn_on_bad_time(context: &Context, now: i64, known_past_timestam
true,
)
.await
.context("Failed to add bad time warning")
.log_err(context)
.ok();
} else {
warn!(context, "Can't convert current timestamp");
@@ -261,7 +263,7 @@ async fn maybe_warn_on_bad_time(context: &Context, now: i64, known_past_timestam
#[expect(clippy::arithmetic_side_effects)]
async fn maybe_warn_on_outdated(context: &Context, now: i64, approx_compile_time: i64) {
if now > approx_compile_time + DC_OUTDATED_WARNING_DAYS * 24 * 60 * 60 {
let mut msg = Message::new_text(stock_str::update_reminder_msg_body(context).await);
let mut msg = Message::new_text(stock_str::update_reminder_msg_body(context));
if let Some(timestamp) = chrono::DateTime::<chrono::Utc>::from_timestamp(now, 0) {
add_device_msg(
context,