mirror of
https://github.com/chatmail/core.git
synced 2026-04-28 19:06:35 +03:00
Strongly type stock strings
This changes the internal stock strings API to be more strongly typed, ensuring that the caller can not construct the stock string in the wrong way. The old approach left it to the callers to figure out how a stock string should be created, now each stock string has their specific arguments and callers can not make mistakes. In particular all the subtleties and different ways of calling stock_system_msg() disappear. This could not use a trait for stock strings, as this would not allow having per-message typed arguments. So we needed a type per message with a custom method, only by convention this method is .stock_str(). The type is a enum without variants to avoid allowing someone to create the type. Sadly the fallback string and substitutions are still far away from each other, but it is now only one place which needs to know how to construct the string instead of many.
This commit is contained in:
141
src/chat.rs
141
src/chat.rs
@@ -1,6 +1,6 @@
|
||||
//! # Chat module
|
||||
|
||||
use deltachat_derive::{FromSql, ToSql};
|
||||
use std::borrow::Cow;
|
||||
use std::convert::TryFrom;
|
||||
use std::str::FromStr;
|
||||
use std::time::{Duration, SystemTime};
|
||||
@@ -8,6 +8,7 @@ use std::time::{Duration, SystemTime};
|
||||
use anyhow::Context as _;
|
||||
use anyhow::{bail, ensure, format_err, Error};
|
||||
use async_std::path::{Path, PathBuf};
|
||||
use deltachat_derive::{FromSql, ToSql};
|
||||
use itertools::Itertools;
|
||||
use num_traits::FromPrimitive;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -38,7 +39,11 @@ use crate::mimeparser::SystemMessage;
|
||||
use crate::param::{Param, Params};
|
||||
use crate::peerstate::{Peerstate, PeerstateVerifiedStatus};
|
||||
use crate::sql;
|
||||
use crate::stock::StockMessage;
|
||||
use crate::stock::{
|
||||
ArchivedChats, DeadDrop, DeviceMessages, E2eAvailable, E2ePreferred, EncrNone, MsgAddMember,
|
||||
MsgDelMember, MsgGroupLeft, MsgGrpImgChanged, MsgGrpImgDeleted, MsgGrpName, NewGroupDraft,
|
||||
SavedMessages, SelfDeletedMsgBody, VideochatInviteMsgBody,
|
||||
};
|
||||
|
||||
/// An chat item, such as a message or a marker.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
@@ -396,12 +401,7 @@ impl ChatId {
|
||||
|
||||
if chat.is_self_talk() {
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.text = Some(
|
||||
context
|
||||
.stock_str(StockMessage::SelfDeletedMsgBody)
|
||||
.await
|
||||
.into(),
|
||||
);
|
||||
msg.text = Some(SelfDeletedMsgBody::stock_str(context).await.into());
|
||||
add_device_msg(&context, None, Some(&mut msg)).await?;
|
||||
}
|
||||
|
||||
@@ -659,23 +659,23 @@ impl ChatId {
|
||||
let addr = contact.get_addr();
|
||||
let peerstate = Peerstate::from_addr(context, addr).await?;
|
||||
|
||||
let stock_message = peerstate
|
||||
let stock_message = match peerstate
|
||||
.filter(|peerstate| {
|
||||
peerstate
|
||||
.peek_key(PeerstateVerifiedStatus::Unverified)
|
||||
.is_some()
|
||||
})
|
||||
.map(|peerstate| match peerstate.prefer_encrypt {
|
||||
EncryptPreference::Mutual => StockMessage::E2ePreferred,
|
||||
EncryptPreference::NoPreference => StockMessage::E2eAvailable,
|
||||
EncryptPreference::Reset => StockMessage::EncrNone,
|
||||
})
|
||||
.unwrap_or(StockMessage::EncrNone);
|
||||
|
||||
.map(|peerstate| peerstate.prefer_encrypt)
|
||||
{
|
||||
Some(EncryptPreference::Mutual) => E2ePreferred::stock_str(context).await,
|
||||
Some(EncryptPreference::NoPreference) => E2eAvailable::stock_str(context).await,
|
||||
Some(EncryptPreference::Reset) => EncrNone::stock_str(context).await,
|
||||
None => EncrNone::stock_str(context).await,
|
||||
};
|
||||
if !ret.is_empty() {
|
||||
ret.push('\n')
|
||||
}
|
||||
ret += &format!("{} {}", addr, context.stock_str(stock_message).await);
|
||||
ret += &format!("{} {}", addr, stock_message);
|
||||
}
|
||||
|
||||
Ok(ret)
|
||||
@@ -793,9 +793,9 @@ impl Chat {
|
||||
}
|
||||
Ok(mut chat) => {
|
||||
if chat.id.is_deaddrop() {
|
||||
chat.name = context.stock_str(StockMessage::DeadDrop).await.into();
|
||||
chat.name = DeadDrop::stock_str(context).await.into();
|
||||
} else if chat.id.is_archived_link() {
|
||||
let tempname = context.stock_str(StockMessage::ArchivedChats).await;
|
||||
let tempname = ArchivedChats::stock_str(context).await;
|
||||
let cnt = dc_get_archived_cnt(context).await;
|
||||
chat.name = format!("{} ({})", tempname, cnt);
|
||||
} else {
|
||||
@@ -810,9 +810,9 @@ impl Chat {
|
||||
chat.name = chat_name;
|
||||
}
|
||||
if chat.param.exists(Param::Selftalk) {
|
||||
chat.name = context.stock_str(StockMessage::SavedMessages).await.into();
|
||||
chat.name = SavedMessages::stock_str(context).await.into();
|
||||
} else if chat.param.exists(Param::Devicetalk) {
|
||||
chat.name = context.stock_str(StockMessage::DeviceMessages).await.into();
|
||||
chat.name = DeviceMessages::stock_str(context).await.into();
|
||||
}
|
||||
}
|
||||
Ok(chat)
|
||||
@@ -1411,10 +1411,10 @@ pub(crate) async fn update_device_icon(context: &Context) -> Result<(), Error> {
|
||||
async fn update_special_chat_name(
|
||||
context: &Context,
|
||||
contact_id: u32,
|
||||
stock_id: StockMessage,
|
||||
name: Cow<'static, str>,
|
||||
) -> Result<(), Error> {
|
||||
if let Ok((chat_id, _)) = lookup_by_contact_id(context, contact_id).await {
|
||||
let name: String = context.stock_str(stock_id).await.into();
|
||||
let name: String = name.into();
|
||||
// the `!= name` condition avoids unneeded writes
|
||||
context
|
||||
.sql
|
||||
@@ -1428,8 +1428,18 @@ async fn update_special_chat_name(
|
||||
}
|
||||
|
||||
pub(crate) async fn update_special_chat_names(context: &Context) -> Result<(), Error> {
|
||||
update_special_chat_name(context, DC_CONTACT_ID_DEVICE, StockMessage::DeviceMessages).await?;
|
||||
update_special_chat_name(context, DC_CONTACT_ID_SELF, StockMessage::SavedMessages).await?;
|
||||
update_special_chat_name(
|
||||
context,
|
||||
DC_CONTACT_ID_DEVICE,
|
||||
DeviceMessages::stock_str(context).await,
|
||||
)
|
||||
.await?;
|
||||
update_special_chat_name(
|
||||
context,
|
||||
DC_CONTACT_ID_SELF,
|
||||
SavedMessages::stock_str(context).await,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1812,12 +1822,9 @@ pub async fn send_videochat_invitation(context: &Context, chat_id: ChatId) -> Re
|
||||
let mut msg = Message::new(Viewtype::VideochatInvitation);
|
||||
msg.param.set(Param::WebrtcRoom, &instance);
|
||||
msg.text = Some(
|
||||
context
|
||||
.stock_string_repl_str(
|
||||
StockMessage::VideochatInviteMsgBody,
|
||||
Message::parse_webrtc_instance(&instance).1,
|
||||
)
|
||||
.await,
|
||||
VideochatInviteMsgBody::stock_str(context, Message::parse_webrtc_instance(&instance).1)
|
||||
.await
|
||||
.to_string(),
|
||||
);
|
||||
send_msg(context, chat_id, &mut msg).await
|
||||
}
|
||||
@@ -2154,9 +2161,9 @@ pub async fn create_group_chat(
|
||||
let chat_name = improve_single_line_input(chat_name);
|
||||
ensure!(!chat_name.is_empty(), "Invalid chat name");
|
||||
|
||||
let draft_txt = context
|
||||
.stock_string_repl_str(StockMessage::NewGroupDraft, &chat_name)
|
||||
.await;
|
||||
let draft_txt = NewGroupDraft::stock_str(context, &chat_name)
|
||||
.await
|
||||
.to_string();
|
||||
let grpid = dc_create_id();
|
||||
|
||||
context.sql.execute(
|
||||
@@ -2334,14 +2341,9 @@ pub(crate) async fn add_contact_to_chat_ex(
|
||||
if chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 0 {
|
||||
msg.viewtype = Viewtype::Text;
|
||||
msg.text = Some(
|
||||
context
|
||||
.stock_system_msg(
|
||||
StockMessage::MsgAddMember,
|
||||
contact.get_addr(),
|
||||
"",
|
||||
DC_CONTACT_ID_SELF as u32,
|
||||
)
|
||||
.await,
|
||||
MsgAddMember::stock_str(context, contact.get_addr(), DC_CONTACT_ID_SELF)
|
||||
.await
|
||||
.to_string(),
|
||||
);
|
||||
msg.param.set_cmd(SystemMessage::MemberAddedToGroup);
|
||||
msg.param.set(Param::Arg, contact.get_addr());
|
||||
@@ -2537,25 +2539,19 @@ pub async fn remove_contact_from_chat(
|
||||
if contact.id == DC_CONTACT_ID_SELF {
|
||||
set_group_explicitly_left(context, chat.grpid).await?;
|
||||
msg.text = Some(
|
||||
context
|
||||
.stock_system_msg(
|
||||
StockMessage::MsgGroupLeft,
|
||||
"",
|
||||
"",
|
||||
DC_CONTACT_ID_SELF,
|
||||
)
|
||||
.await,
|
||||
MsgGroupLeft::stock_str(context, DC_CONTACT_ID_SELF)
|
||||
.await
|
||||
.to_string(),
|
||||
);
|
||||
} else {
|
||||
msg.text = Some(
|
||||
context
|
||||
.stock_system_msg(
|
||||
StockMessage::MsgDelMember,
|
||||
contact.get_addr(),
|
||||
"",
|
||||
DC_CONTACT_ID_SELF,
|
||||
)
|
||||
.await,
|
||||
MsgDelMember::stock_str(
|
||||
context,
|
||||
contact.get_addr(),
|
||||
DC_CONTACT_ID_SELF,
|
||||
)
|
||||
.await
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
msg.param.set_cmd(SystemMessage::MemberRemovedFromGroup);
|
||||
@@ -2651,14 +2647,9 @@ pub async fn set_chat_name(
|
||||
if chat.is_promoted() && !chat.is_mailing_list() {
|
||||
msg.viewtype = Viewtype::Text;
|
||||
msg.text = Some(
|
||||
context
|
||||
.stock_system_msg(
|
||||
StockMessage::MsgGrpName,
|
||||
&chat.name,
|
||||
&new_name,
|
||||
DC_CONTACT_ID_SELF,
|
||||
)
|
||||
.await,
|
||||
MsgGrpName::stock_str(context, &chat.name, &new_name, DC_CONTACT_ID_SELF)
|
||||
.await
|
||||
.to_string(),
|
||||
);
|
||||
msg.param.set_cmd(SystemMessage::GroupNameChanged);
|
||||
if !chat.name.is_empty() {
|
||||
@@ -2716,9 +2707,9 @@ pub async fn set_chat_profile_image(
|
||||
chat.param.remove(Param::ProfileImage);
|
||||
msg.param.remove(Param::Arg);
|
||||
msg.text = Some(
|
||||
context
|
||||
.stock_system_msg(StockMessage::MsgGrpImgDeleted, "", "", DC_CONTACT_ID_SELF)
|
||||
.await,
|
||||
MsgGrpImgDeleted::stock_str(context, DC_CONTACT_ID_SELF)
|
||||
.await
|
||||
.to_string(),
|
||||
);
|
||||
} else {
|
||||
let image_blob = match BlobObject::from_path(context, Path::new(new_image.as_ref())) {
|
||||
@@ -2734,9 +2725,9 @@ 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 = Some(
|
||||
context
|
||||
.stock_system_msg(StockMessage::MsgGrpImgChanged, "", "", DC_CONTACT_ID_SELF)
|
||||
.await,
|
||||
MsgGrpImgChanged::stock_str(context, DC_CONTACT_ID_SELF)
|
||||
.await
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
chat.update_param(context).await?;
|
||||
@@ -3215,7 +3206,7 @@ mod tests {
|
||||
assert!(chat.visibility == ChatVisibility::Normal);
|
||||
assert!(!chat.is_device_talk());
|
||||
assert!(chat.can_send());
|
||||
assert_eq!(chat.name, t.stock_str(StockMessage::SavedMessages).await);
|
||||
assert_eq!(chat.name, SavedMessages::stock_str(&t).await);
|
||||
assert!(chat.get_profile_image(&t).await.is_some());
|
||||
}
|
||||
|
||||
@@ -3231,7 +3222,7 @@ mod tests {
|
||||
assert!(chat.visibility == ChatVisibility::Normal);
|
||||
assert!(!chat.is_device_talk());
|
||||
assert!(!chat.can_send());
|
||||
assert_eq!(chat.name, t.stock_str(StockMessage::DeadDrop).await);
|
||||
assert_eq!(chat.name, DeadDrop::stock_str(&t).await);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
@@ -3308,7 +3299,7 @@ mod tests {
|
||||
assert!(chat.is_device_talk());
|
||||
assert!(!chat.is_self_talk());
|
||||
assert!(!chat.can_send());
|
||||
assert_eq!(chat.name, t.stock_str(StockMessage::DeviceMessages).await);
|
||||
assert_eq!(chat.name, DeviceMessages::stock_str(&t).await);
|
||||
assert!(chat.get_profile_image(&t).await.is_some());
|
||||
|
||||
// delete device message, make sure it is not added again
|
||||
|
||||
Reference in New Issue
Block a user