mirror of
https://github.com/chatmail/core.git
synced 2026-04-03 05:52:10 +03:00
Compare commits
12 Commits
v2.47.0
...
link2xt/de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ac9c6bb09 | ||
|
|
84459b6495 | ||
|
|
c99b8a4482 | ||
|
|
76e2c36d85 | ||
|
|
1b8bf4ed23 | ||
|
|
c553357c60 | ||
|
|
ebe8550c52 | ||
|
|
2637c3bea4 | ||
|
|
d1f1633c60 | ||
|
|
98b55ec15f | ||
|
|
6a3ef20a99 | ||
|
|
59be03a7eb |
@@ -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
35
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -54,5 +54,5 @@
|
||||
},
|
||||
"type": "module",
|
||||
"types": "dist/deltachat.d.ts",
|
||||
"version": "2.47.0"
|
||||
"version": "2.48.0-dev"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -15,5 +15,5 @@
|
||||
},
|
||||
"type": "module",
|
||||
"types": "index.d.ts",
|
||||
"version": "2.47.0"
|
||||
"version": "2.48.0-dev"
|
||||
}
|
||||
|
||||
@@ -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" },
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
31
src/calls.rs
31
src/calls.rs
@@ -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?;
|
||||
}
|
||||
}
|
||||
|
||||
37
src/chat.rs
37
src/chat.rs
@@ -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
|
||||
};
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
_ => {}
|
||||
|
||||
@@ -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.
|
||||
|
||||
100
src/imap.rs
100
src/imap.rs
@@ -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?;
|
||||
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}]"))
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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,
|
||||
|
||||
24
src/quota.rs
24
src/quota.rs
@@ -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("a.modified) < Duration::from_secs(ratelimit_secs))
|
||||
.is_none()
|
||||
quota.get(&transport_id).is_none_or(|quota| {
|
||||
time_elapsed("a.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) = "a {
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>";
|
||||
|
||||
// =============================================================================================
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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?;
|
||||
}
|
||||
|
||||
414
src/stock_str.rs
414
src/stock_str.rs
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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("%%"));
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user