diff --git a/src/chat.rs b/src/chat.rs index 533df33b9..ccffe2fb0 100644 --- a/src/chat.rs +++ b/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 diff --git a/src/chatlist.rs b/src/chatlist.rs index 8d080d0d6..6d8f610f1 100644 --- a/src/chatlist.rs +++ b/src/chatlist.rs @@ -14,7 +14,7 @@ use crate::context::Context; use crate::ephemeral::delete_expired_messages; use crate::lot::Lot; use crate::message::{Message, MessageState, MsgId}; -use crate::stock::StockMessage; +use crate::stock::NoMessages; /// An object representing a single chatlist in memory. /// @@ -385,12 +385,7 @@ impl Chatlist { ret.text2 = None; } else if lastmsg.is_none() || lastmsg.as_ref().unwrap().from_id == DC_CONTACT_ID_UNDEFINED { - ret.text2 = Some( - context - .stock_str(StockMessage::NoMessages) - .await - .to_string(), - ); + ret.text2 = Some(NoMessages::stock_str(context).await.to_string()); } else { ret.fill(&mut lastmsg.unwrap(), chat, lastcontact.as_ref(), context) .await; @@ -445,6 +440,7 @@ mod tests { use crate::chat::{create_group_chat, ProtectionStatus}; use crate::constants::Viewtype; + use crate::stock::StockMessage; use crate::test_utils::TestContext; #[async_std::test] diff --git a/src/config.rs b/src/config.rs index 654949176..c4aba0357 100644 --- a/src/config.rs +++ b/src/config.rs @@ -3,6 +3,7 @@ use strum::{EnumProperty, IntoEnumIterator}; use strum_macros::{AsRefStr, Display, EnumIter, EnumProperty, EnumString}; +use crate::blob::BlobObject; use crate::chat::ChatId; use crate::constants::DC_VERSION_STR; use crate::context::Context; @@ -11,11 +12,8 @@ use crate::events::EventType; use crate::job; use crate::message::MsgId; use crate::mimefactory::RECOMMENDED_FILE_SIZE; -use crate::stock::StockMessage; -use crate::{ - blob::BlobObject, - provider::{get_provider_by_id, Provider}, -}; +use crate::provider::{get_provider_by_id, Provider}; +use crate::stock::StatusLine; /// The available configuration keys. #[derive( @@ -175,7 +173,7 @@ impl Context { // Default values match key { - Config::Selfstatus => Some(self.stock_str(StockMessage::StatusLine).await.into_owned()), + Config::Selfstatus => Some(StatusLine::stock_str(self).await.into_owned()), Config::ConfiguredInboxFolder => Some("INBOX".to_owned()), _ => key.get_str("default").map(|s| s.to_string()), } @@ -260,7 +258,7 @@ impl Context { } } Config::Selfstatus => { - let def = self.stock_str(StockMessage::StatusLine).await; + let def = StatusLine::stock_str(self).await; let val = if value.is_none() || value.unwrap() == def { None } else { diff --git a/src/configure/mod.rs b/src/configure/mod.rs index 0cb1a35c8..e23764d18 100644 --- a/src/configure/mod.rs +++ b/src/configure/mod.rs @@ -19,7 +19,7 @@ use crate::message::Message; use crate::oauth2::dc_get_oauth2_addr; use crate::provider::{Protocol, Socket, UsernamePattern}; use crate::smtp::Smtp; -use crate::stock::StockMessage; +use crate::stock::{ConfigurationFailed, ErrorNoNetwork}; use crate::{chat, e2ee, provider}; use crate::{config::Config, dc_tools::time}; use crate::{ @@ -127,12 +127,14 @@ impl Context { self, 0, Some( - self.stock_string_repl_str( - StockMessage::ConfigurationFailed, - // We are using Anyhow's .context() and to show the inner error, too, we need the {:#}: + ConfigurationFailed::stock_str( + self, + // We are using Anyhow's .context() and to show the + // inner error, too, we need the {:#}: format!("{:#}", err), ) .await + .to_string() ) ); Err(err) @@ -589,10 +591,7 @@ async fn nicer_configuration_error(context: &Context, errors: Vec StockMessage::E2ePreferred, - EncryptPreference::NoPreference => StockMessage::E2eAvailable, - EncryptPreference::Reset => StockMessage::EncrNone, + EncryptPreference::Mutual => E2ePreferred::stock_str(context).await, + EncryptPreference::NoPreference => E2eAvailable::stock_str(context).await, + EncryptPreference::Reset => EncrNone::stock_str(context).await, }; ret += &format!( "{}\n{}:", - context.stock_str(stock_message).await, - context.stock_str(StockMessage::FingerPrints).await + stock_message, + FingerPrints::stock_str(context).await ); let fingerprint_self = SignedPublicKey::load_self(context) @@ -770,7 +767,7 @@ impl Contact { cat_fingerprint(&mut ret, &loginparam.addr, &fingerprint_self, ""); } } else { - ret += &context.stock_str(StockMessage::EncrNone).await; + ret += &EncrNone::stock_str(context).await; } } @@ -1510,7 +1507,7 @@ mod tests { // check SELF let contact = Contact::load_from_db(&t, DC_CONTACT_ID_SELF).await.unwrap(); assert_eq!(DC_CONTACT_ID_SELF, 1); - assert_eq!(contact.get_name(), t.stock_str(StockMessage::SelfMsg).await); + assert_eq!(contact.get_name(), SelfMsg::stock_str(&t).await); assert_eq!(contact.get_addr(), ""); // we're not configured assert!(!contact.is_blocked()); } diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index 251692840..c1b15c912 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -26,7 +26,10 @@ use crate::mimeparser::{parse_message_ids, AvatarAction, MimeMessage, SystemMess use crate::param::{Param, Params}; use crate::peerstate::{Peerstate, PeerstateKeyType, PeerstateVerifiedStatus}; use crate::securejoin::{self, handle_securejoin_handshake, observe_securejoin_on_other_device}; -use crate::stock::StockMessage; +use crate::stock::{ + MsgAddMember, MsgDelMember, MsgGroupLeft, MsgGrpImgChanged, MsgGrpImgDeleted, MsgGrpName, + MsgLocationEnabled, UnknownSenderForChat, +}; use crate::{contact, location}; // IndexSet is like HashSet but maintains order of insertion @@ -1165,9 +1168,9 @@ async fn create_or_lookup_group( let mut better_msg: String = From::from(""); if mime_parser.is_system_message == SystemMessage::LocationStreamingEnabled { - better_msg = context - .stock_system_msg(StockMessage::MsgLocationEnabled, "", "", from_id as u32) - .await; + better_msg = MsgLocationEnabled::stock_str_by(context, from_id) + .await + .to_string(); set_better_msg(mime_parser, &better_msg); } @@ -1231,48 +1234,38 @@ async fn create_or_lookup_group( match removed_id { Some(contact_id) => { mime_parser.is_system_message = SystemMessage::MemberRemovedFromGroup; - better_msg = context - .stock_system_msg( - if contact_id == from_id as u32 { - StockMessage::MsgGroupLeft - } else { - StockMessage::MsgDelMember - }, - &removed_addr, - "", - from_id as u32, - ) - .await; + better_msg = if contact_id == from_id { + MsgGroupLeft::stock_str(context, from_id).await.to_string() + } else { + MsgDelMember::stock_str(context, &removed_addr, from_id) + .await + .to_string() + }; } None => warn!(context, "removed {:?} has no contact_id", removed_addr), } } else { let field = mime_parser.get(HeaderDef::ChatGroupMemberAdded).cloned(); - if let Some(optional_field) = field { + if let Some(added_member) = field { mime_parser.is_system_message = SystemMessage::MemberAddedToGroup; - better_msg = context - .stock_system_msg( - StockMessage::MsgAddMember, - &optional_field, - "", - from_id as u32, - ) - .await; - X_MrAddToGrp = Some(optional_field); + better_msg = MsgAddMember::stock_str(context, &added_member, from_id) + .await + .to_string(); + X_MrAddToGrp = Some(added_member); } else if let Some(old_name) = mime_parser.get(HeaderDef::ChatGroupNameChanged) { X_MrGrpNameChanged = true; - better_msg = context - .stock_system_msg( - StockMessage::MsgGrpName, - old_name, - if let Some(ref name) = grpname { - name - } else { - "" - }, - from_id as u32, - ) - .await; + better_msg = MsgGrpName::stock_str( + context, + old_name, + if let Some(ref name) = grpname { + name + } else { + "" + }, + from_id as u32, + ) + .await + .to_string(); mime_parser.is_system_message = SystemMessage::GroupNameChanged; } else if let Some(value) = mime_parser.get(HeaderDef::ChatContent) { if value == "group-avatar-changed" { @@ -1280,17 +1273,14 @@ async fn create_or_lookup_group( // this is just an explicit message containing the group-avatar, // apart from that, the group-avatar is send along with various other messages mime_parser.is_system_message = SystemMessage::GroupImageChanged; - better_msg = context - .stock_system_msg( - match avatar_action { - AvatarAction::Delete => StockMessage::MsgGrpImgDeleted, - AvatarAction::Change(_) => StockMessage::MsgGrpImgChanged, - }, - "", - "", - from_id as u32, - ) - .await + better_msg = match avatar_action { + AvatarAction::Delete => MsgGrpImgDeleted::stock_str(context, from_id) + .await + .to_string(), + AvatarAction::Change(_) => MsgGrpImgChanged::stock_str(context, from_id) + .await + .to_string(), + }; } } } @@ -1308,7 +1298,7 @@ async fn create_or_lookup_group( // but still show the message as part of the chat. // After all, the sender has a reference/in-reply-to that // points to this chat. - let s = context.stock_str(StockMessage::UnknownSenderForChat).await; + let s = UnknownSenderForChat::stock_str(context).await; mime_parser.repl_msg_by_error(s.to_string()); } @@ -1989,6 +1979,7 @@ mod tests { use crate::constants::{DC_CONTACT_ID_INFO, DC_GCL_NO_SPECIALS}; use crate::message::ContactRequestDecision::*; use crate::message::Message; + use crate::stock::FailedSendingTo; use crate::test_utils::TestContext; use crate::{ chat::{ChatItem, ChatVisibility}, @@ -2656,11 +2647,9 @@ mod tests { assert_eq!( last_msg.text, Some( - t.stock_string_repl_str( - StockMessage::FailedSendingTo, - "assidhfaaspocwaeofi@gmail.com", - ) - .await, + FailedSendingTo::stock_str(&t, "assidhfaaspocwaeofi@gmail.com") + .await + .to_string(), ) ); assert_eq!(last_msg.from_id, DC_CONTACT_ID_INFO); diff --git a/src/dc_tools.rs b/src/dc_tools.rs index 19503b0df..1cd1d5fd6 100644 --- a/src/dc_tools.rs +++ b/src/dc_tools.rs @@ -22,7 +22,7 @@ use crate::context::Context; use crate::events::EventType; use crate::message::Message; use crate::provider::get_provider_update_timestamp; -use crate::stock::StockMessage; +use crate::stock::{BadTimeMsgBody, UpdateReminderMsgBody}; /// Shortens a string to a specified length and adds "[...]" to the /// end of the shortened string. @@ -169,15 +169,15 @@ async fn maybe_warn_on_bad_time(context: &Context, now: i64, known_past_timestam if now < known_past_timestamp { let mut msg = Message::new(Viewtype::Text); msg.text = Some( - context - .stock_string_repl_str( - StockMessage::BadTimeMsgBody, - Local - .timestamp(now, 0) - .format("%Y-%m-%d %H:%M:%S") - .to_string(), - ) - .await, + BadTimeMsgBody::stock_str( + context, + Local + .timestamp(now, 0) + .format("%Y-%m-%d %H:%M:%S") + .to_string(), + ) + .await + .to_string(), ); add_device_msg_with_importance( context, @@ -201,12 +201,7 @@ async fn maybe_warn_on_bad_time(context: &Context, now: i64, known_past_timestam 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(Viewtype::Text); - msg.text = Some( - context - .stock_str(StockMessage::UpdateReminderMsgBody) - .await - .into(), - ); + msg.text = Some(UpdateReminderMsgBody::stock_str(context).await.into()); add_device_msg( context, Some( diff --git a/src/ephemeral.rs b/src/ephemeral.rs index 76bd66620..ab2e82996 100644 --- a/src/ephemeral.rs +++ b/src/ephemeral.rs @@ -56,7 +56,15 @@ //! the database entries which are expired either according to their //! ephemeral message timers or global `delete_server_after` setting. +use std::borrow::Cow; +use std::convert::{TryFrom, TryInto}; +use std::num::ParseIntError; +use std::str::FromStr; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + use anyhow::{ensure, Error}; +use async_std::task; +use serde::{Deserialize, Serialize}; use crate::chat::{lookup_by_contact_id, send_msg, ChatId}; use crate::constants::{ @@ -68,13 +76,12 @@ use crate::events::EventType; use crate::message::{Message, MessageState, MsgId}; use crate::mimeparser::SystemMessage; use crate::sql; -use crate::stock::StockMessage; -use async_std::task; -use serde::{Deserialize, Serialize}; -use std::convert::{TryFrom, TryInto}; -use std::num::ParseIntError; -use std::str::FromStr; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use crate::stock::{ + MsgEphemeralTimerDay, MsgEphemeralTimerDays, MsgEphemeralTimerDisabled, + MsgEphemeralTimerEnabled, MsgEphemeralTimerHour, MsgEphemeralTimerHours, + MsgEphemeralTimerMinute, MsgEphemeralTimerMinutes, MsgEphemeralTimerWeek, + MsgEphemeralTimerWeeks, +}; #[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] pub enum Timer { @@ -194,7 +201,11 @@ impl ChatId { } self.inner_set_ephemeral_timer(context, timer).await?; let mut msg = Message::new(Viewtype::Text); - msg.text = Some(stock_ephemeral_timer_changed(context, timer, DC_CONTACT_ID_SELF).await); + msg.text = Some( + stock_ephemeral_timer_changed(context, timer, DC_CONTACT_ID_SELF) + .await + .to_string(), + ); msg.param.set_cmd(SystemMessage::EphemeralTimerChanged); if let Err(err) = send_msg(context, self, &mut msg).await { error!( @@ -211,87 +222,48 @@ pub(crate) async fn stock_ephemeral_timer_changed( context: &Context, timer: Timer, from_id: u32, -) -> String { +) -> Cow<'static, str> { match timer { - Timer::Disabled => { - context - .stock_system_msg( - StockMessage::MsgEphemeralTimerDisabled, - timer.to_string(), - "", + Timer::Disabled => MsgEphemeralTimerDisabled::stock_str(context, from_id).await, + Timer::Enabled { duration } => match duration { + 0..=59 => { + MsgEphemeralTimerEnabled::stock_str(context, timer.to_string(), from_id).await + } + 60 => MsgEphemeralTimerMinute::stock_str(context, from_id).await, + 61..=3599 => { + MsgEphemeralTimerMinutes::stock_str( + context, + format!("{}", (f64::from(duration) / 6.0).round() / 10.0), from_id, ) .await - } - Timer::Enabled { duration } => match duration { - 0..=59 => { - context - .stock_system_msg( - StockMessage::MsgEphemeralTimerEnabled, - timer.to_string(), - "", - from_id, - ) - .await - } - 60 => { - context - .stock_system_msg(StockMessage::MsgEphemeralTimerMinute, "", "", from_id) - .await - } - 61..=3599 => { - context - .stock_system_msg( - StockMessage::MsgEphemeralTimerMinutes, - format!("{}", (f64::from(duration) / 6.0).round() / 10.0), - "", - from_id, - ) - .await - } - 3600 => { - context - .stock_system_msg(StockMessage::MsgEphemeralTimerHour, "", "", from_id) - .await } + 3600 => MsgEphemeralTimerHour::stock_str(context, from_id).await, 3601..=86399 => { - context - .stock_system_msg( - StockMessage::MsgEphemeralTimerHours, - format!("{}", (f64::from(duration) / 360.0).round() / 10.0), - "", - from_id, - ) - .await - } - 86400 => { - context - .stock_system_msg(StockMessage::MsgEphemeralTimerDay, "", "", from_id) - .await + MsgEphemeralTimerHours::stock_str( + context, + format!("{}", (f64::from(duration) / 360.0).round() / 10.0), + from_id, + ) + .await } + 86400 => MsgEphemeralTimerDay::stock_str(context, from_id).await, 86401..=604_799 => { - context - .stock_system_msg( - StockMessage::MsgEphemeralTimerDays, - format!("{}", (f64::from(duration) / 8640.0).round() / 10.0), - "", - from_id, - ) - .await - } - 604_800 => { - { context.stock_system_msg(StockMessage::MsgEphemeralTimerWeek, "", "", from_id) } - .await + MsgEphemeralTimerDays::stock_str( + context, + format!("{}", (f64::from(duration) / 8640.0).round() / 10.0), + from_id, + ) + .await } + 604_800 => MsgEphemeralTimerWeek::stock_str(context, from_id).await, _ => { - context - .stock_system_msg( - StockMessage::MsgEphemeralTimerWeeks, - format!("{}", (f64::from(duration) / 60480.0).round() / 10.0), - "", - from_id, - ) - .await + MsgEphemeralTimerWeeks::stock_str( + context, + format!("{}", (f64::from(duration) / 60480.0).round() / 10.0), + from_id, + ) + .await } }, } @@ -547,36 +519,67 @@ mod tests { ); assert_eq!( - stock_ephemeral_timer_changed(&context, Timer::Disabled, 0).await, - "Message deletion timer is disabled." + stock_ephemeral_timer_changed( + &context, + Timer::Enabled { duration: 1 }, + DC_CONTACT_ID_SELF + ) + .await, + "Message deletion timer is set to 1 s by me." ); assert_eq!( - stock_ephemeral_timer_changed(&context, Timer::Enabled { duration: 1 }, 0).await, - "Message deletion timer is set to 1 s." + stock_ephemeral_timer_changed( + &context, + Timer::Enabled { duration: 30 }, + DC_CONTACT_ID_SELF + ) + .await, + "Message deletion timer is set to 30 s by me." ); assert_eq!( - stock_ephemeral_timer_changed(&context, Timer::Enabled { duration: 30 }, 0).await, - "Message deletion timer is set to 30 s." + stock_ephemeral_timer_changed( + &context, + Timer::Enabled { duration: 60 }, + DC_CONTACT_ID_SELF + ) + .await, + "Message deletion timer is set to 1 minute by me." ); assert_eq!( - stock_ephemeral_timer_changed(&context, Timer::Enabled { duration: 60 }, 0).await, - "Message deletion timer is set to 1 minute." + stock_ephemeral_timer_changed( + &context, + Timer::Enabled { duration: 90 }, + DC_CONTACT_ID_SELF + ) + .await, + "Message deletion timer is set to 1.5 minutes by me." ); assert_eq!( - stock_ephemeral_timer_changed(&context, Timer::Enabled { duration: 90 }, 0).await, - "Message deletion timer is set to 1.5 minutes." + stock_ephemeral_timer_changed( + &context, + Timer::Enabled { duration: 30 * 60 }, + DC_CONTACT_ID_SELF + ) + .await, + "Message deletion timer is set to 30 minutes by me." ); assert_eq!( - stock_ephemeral_timer_changed(&context, Timer::Enabled { duration: 30 * 60 }, 0).await, - "Message deletion timer is set to 30 minutes." + stock_ephemeral_timer_changed( + &context, + Timer::Enabled { duration: 60 * 60 }, + DC_CONTACT_ID_SELF + ) + .await, + "Message deletion timer is set to 1 hour by me." ); assert_eq!( - stock_ephemeral_timer_changed(&context, Timer::Enabled { duration: 60 * 60 }, 0).await, - "Message deletion timer is set to 1 hour." - ); - assert_eq!( - stock_ephemeral_timer_changed(&context, Timer::Enabled { duration: 5400 }, 0).await, - "Message deletion timer is set to 1.5 hours." + stock_ephemeral_timer_changed( + &context, + Timer::Enabled { duration: 5400 }, + DC_CONTACT_ID_SELF + ) + .await, + "Message deletion timer is set to 1.5 hours by me." ); assert_eq!( stock_ephemeral_timer_changed( @@ -584,10 +587,10 @@ mod tests { Timer::Enabled { duration: 2 * 60 * 60 }, - 0 + DC_CONTACT_ID_SELF ) .await, - "Message deletion timer is set to 2 hours." + "Message deletion timer is set to 2 hours by me." ); assert_eq!( stock_ephemeral_timer_changed( @@ -595,10 +598,10 @@ mod tests { Timer::Enabled { duration: 24 * 60 * 60 }, - 0 + DC_CONTACT_ID_SELF ) .await, - "Message deletion timer is set to 1 day." + "Message deletion timer is set to 1 day by me." ); assert_eq!( stock_ephemeral_timer_changed( @@ -606,10 +609,10 @@ mod tests { Timer::Enabled { duration: 2 * 24 * 60 * 60 }, - 0 + DC_CONTACT_ID_SELF ) .await, - "Message deletion timer is set to 2 days." + "Message deletion timer is set to 2 days by me." ); assert_eq!( stock_ephemeral_timer_changed( @@ -617,10 +620,10 @@ mod tests { Timer::Enabled { duration: 7 * 24 * 60 * 60 }, - 0 + DC_CONTACT_ID_SELF ) .await, - "Message deletion timer is set to 1 week." + "Message deletion timer is set to 1 week by me." ); assert_eq!( stock_ephemeral_timer_changed( @@ -628,10 +631,10 @@ mod tests { Timer::Enabled { duration: 4 * 7 * 24 * 60 * 60 }, - 0 + DC_CONTACT_ID_SELF ) .await, - "Message deletion timer is set to 4 weeks." + "Message deletion timer is set to 4 weeks by me." ); } diff --git a/src/imap/mod.rs b/src/imap/mod.rs index f7a248461..f0eab58a1 100644 --- a/src/imap/mod.rs +++ b/src/imap/mod.rs @@ -14,12 +14,17 @@ use async_std::channel::Receiver; use async_std::prelude::*; use num_traits::FromPrimitive; +use crate::chat; +use crate::config::Config; use crate::constants::{ Chattype, ShowEmails, Viewtype, DC_CONTACT_ID_SELF, DC_FETCH_EXISTING_MSGS_COUNT, DC_FOLDERS_CONFIGURED_VERSION, DC_LP_AUTH_OAUTH2, }; use crate::context::Context; -use crate::dc_receive_imf::{from_field_to_contact_id, get_prefetch_parent_message}; +use crate::dc_receive_imf::{ + dc_receive_imf_inner, from_field_to_contact_id, get_prefetch_parent_message, +}; +use crate::dc_tools::dc_extract_grpid_from_rfc724_mid; use crate::events::EventType; use crate::headerdef::{HeaderDef, HeaderDefMap}; use crate::job::{self, Action}; @@ -29,10 +34,8 @@ use crate::mimeparser; use crate::oauth2::dc_get_oauth2_access_token; use crate::param::Params; use crate::provider::Socket; -use crate::{ - chat, dc_tools::dc_extract_grpid_from_rfc724_mid, scheduler::InterruptInfo, stock::StockMessage, -}; -use crate::{config::Config, dc_receive_imf::dc_receive_imf_inner}; +use crate::scheduler::InterruptInfo; +use crate::stock::CannotLogin; mod client; mod idle; @@ -252,9 +255,9 @@ impl Imap { Err((err, _)) => { let imap_user = self.config.lp.user.to_owned(); - let message = context - .stock_string_repl_str(StockMessage::CannotLogin, &imap_user) - .await; + let message = CannotLogin::stock_str(context, &imap_user) + .await + .to_string(); warn!(context, "{} ({})", message, err); diff --git a/src/imex.rs b/src/imex.rs index c8d76a4b8..1b36528c2 100644 --- a/src/imex.rs +++ b/src/imex.rs @@ -29,7 +29,7 @@ use crate::mimeparser::SystemMessage; use crate::param::Param; use crate::pgp; use crate::sql::{self, Sql}; -use crate::stock::StockMessage; +use crate::stock::{AcSetupMsgBody, AcSetupMsgSubject}; use ::pgp::types::KeyTrait; use async_tar::Archive; @@ -275,8 +275,8 @@ pub async fn render_setup_file(context: &Context, passphrase: &str) -> Result"); Ok(format!( concat!( @@ -902,8 +902,11 @@ where #[cfg(test)] mod tests { use super::*; + use crate::pgp::{split_armored_data, HEADER_AUTOCRYPT, HEADER_SETUPCODE}; + use crate::stock::StockMessage; use crate::test_utils::{alice_keypair, TestContext}; + use ::pgp::armor::BlockType; #[async_std::test] diff --git a/src/location.rs b/src/location.rs index ec0482582..67afda02f 100644 --- a/src/location.rs +++ b/src/location.rs @@ -14,7 +14,7 @@ use crate::job::{self, Job}; use crate::message::{Message, MsgId}; use crate::mimeparser::SystemMessage; use crate::param::Params; -use crate::stock::StockMessage; +use crate::stock::{MsgLocationDisabled, MsgLocationEnabled}; /// Location record #[derive(Debug, Clone, Default)] @@ -212,19 +212,13 @@ pub async fn send_locations_to_chat(context: &Context, chat_id: ChatId, seconds: { if 0 != seconds && !is_sending_locations_before { let mut msg = Message::new(Viewtype::Text); - msg.text = Some( - context - .stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0) - .await, - ); + msg.text = Some(MsgLocationEnabled::stock_str(context).await.to_string()); 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 = context - .stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0) - .await; + let stock_str = MsgLocationDisabled::stock_str(context).await; chat::add_info_msg(context, chat_id, stock_str).await; } context.emit_event(EventType::ChatModified(chat_id)); @@ -716,9 +710,7 @@ pub(crate) async fn job_maybe_send_locations_ended( paramsv![chat_id], ).await); - let stock_str = context - .stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0) - .await; + let stock_str = MsgLocationDisabled::stock_str(context).await; chat::add_info_msg(context, chat_id, stock_str).await; context.emit_event(EventType::ChatModified(chat_id)); } diff --git a/src/message.rs b/src/message.rs index 74b8254f2..887b6f41c 100644 --- a/src/message.rs +++ b/src/message.rs @@ -26,7 +26,10 @@ use crate::lot::{Lot, LotState, Meaning}; use crate::mimeparser::{FailureReport, SystemMessage}; use crate::param::{Param, Params}; use crate::pgp::split_armored_data; -use crate::stock::StockMessage; +use crate::stock::{ + AcSetupMsgSubject, Audio, Draft, FailedSendingTo, File, Gif, Image, Location, ReplyNoun, + SelfMsg, Sticker, Video, VideochatInvitation, VoiceMessage, +}; use std::collections::BTreeMap; // In practice, the user additionally cuts the string themselves @@ -1055,26 +1058,14 @@ impl Lot { context: &Context, ) { if msg.state == MessageState::OutDraft { - self.text1 = Some( - context - .stock_str(StockMessage::Draft) - .await - .to_owned() - .into(), - ); + self.text1 = Some(Draft::stock_str(context).await.to_owned().into()); self.text1_meaning = Meaning::Text1Draft; } else if msg.from_id == DC_CONTACT_ID_SELF { if msg.is_info() || chat.is_self_talk() { self.text1 = None; self.text1_meaning = Meaning::None; } else { - self.text1 = Some( - context - .stock_str(StockMessage::SelfMsg) - .await - .to_owned() - .into(), - ); + self.text1 = Some(SelfMsg::stock_str(context).await.to_owned().into()); self.text1_meaning = Meaning::Text1Self; } } else { @@ -1107,10 +1098,7 @@ impl Lot { .await; if text2.is_empty() && msg.quoted_text().is_some() { - text2 = context - .stock_str(StockMessage::ReplyNoun) - .await - .into_owned() + text2 = ReplyNoun::stock_str(context).await.into_owned() } self.text2 = Some(text2); @@ -1562,21 +1550,15 @@ pub async fn get_summarytext_by_raw( ) -> String { let mut append_text = true; let prefix = match viewtype { - Viewtype::Image => context.stock_str(StockMessage::Image).await.into_owned(), - Viewtype::Gif => context.stock_str(StockMessage::Gif).await.into_owned(), - Viewtype::Sticker => context.stock_str(StockMessage::Sticker).await.into_owned(), - Viewtype::Video => context.stock_str(StockMessage::Video).await.into_owned(), - Viewtype::Voice => context - .stock_str(StockMessage::VoiceMessage) - .await - .into_owned(), + Viewtype::Image => Image::stock_str(context).await.into_owned(), + Viewtype::Gif => Gif::stock_str(context).await.into_owned(), + Viewtype::Sticker => Sticker::stock_str(context).await.into_owned(), + Viewtype::Video => Video::stock_str(context).await.into_owned(), + Viewtype::Voice => VoiceMessage::stock_str(context).await.into_owned(), Viewtype::Audio | Viewtype::File => { if param.get_cmd() == SystemMessage::AutocryptSetupMessage { append_text = false; - context - .stock_str(StockMessage::AcSetupMsgSubject) - .await - .to_string() + AcSetupMsgSubject::stock_str(context).await.to_string() } else { let file_name: String = param .get_path(Param::File, context) @@ -1586,29 +1568,24 @@ pub async fn get_summarytext_by_raw( .map(|fname| fname.to_string_lossy().into_owned()) }) .unwrap_or_else(|| String::from("ErrFileName")); - let label = context - .stock_str(if viewtype == Viewtype::Audio { - StockMessage::Audio - } else { - StockMessage::File - }) - .await; + let label = if viewtype == Viewtype::Audio { + Audio::stock_str(context).await + } else { + File::stock_str(context).await + }; format!("{} – {}", label, file_name) } } Viewtype::VideochatInvitation => { append_text = false; - context - .stock_str(StockMessage::VideochatInvitation) - .await - .into_owned() + VideochatInvitation::stock_str(context).await.into_owned() } _ => { if param.get_cmd() != SystemMessage::LocationOnly { "".to_string() } else { append_text = false; - context.stock_str(StockMessage::Location).await.to_string() + Location::stock_str(context).await.to_string() } } }; @@ -1865,13 +1842,9 @@ async fn ndn_maybe_add_info_msg( Error::msg("ndn_maybe_add_info_msg: Contact ID not found") })?; let contact = Contact::load_from_db(context, contact_id).await?; - // Tell the user which of the recipients failed if we know that (because in a group, this might otherwise be unclear) - let text = context - .stock_string_repl_str( - StockMessage::FailedSendingTo, - contact.get_display_name(), - ) - .await; + // Tell the user which of the recipients failed if we know that (because in + // a group, this might otherwise be unclear) + let text = FailedSendingTo::stock_str(context, contact.get_display_name()).await; chat::add_info_msg(context, chat_id, text).await; context.emit_event(EventType::ChatModified(chat_id)); } diff --git a/src/mimefactory.rs b/src/mimefactory.rs index 8aae58269..1bec369f4 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -21,7 +21,10 @@ use crate::mimeparser::SystemMessage; use crate::param::Param; use crate::peerstate::{Peerstate, PeerstateVerifiedStatus}; use crate::simplify::escape_message_footer_marks; -use crate::stock::StockMessage; +use crate::stock::{ + AcSetupMsgBody, AcSetupMsgSubject, EncryptedMsg, ReadRcpt, ReadRcptMailBody, StatusLine, + SubjectForNewContact, +}; use std::convert::TryInto; // attachments of 25 mb brutto should work on the majority of providers @@ -139,10 +142,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> { ) .await?; - let default_str = context - .stock_str(StockMessage::StatusLine) - .await - .to_string(); + let default_str = StatusLine::stock_str(context).await.to_string(); let factory = MimeFactory { from_addr, from_displayname, @@ -180,10 +180,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> { .get_config(Config::Displayname) .await .unwrap_or_default(); - let default_str = context - .stock_str(StockMessage::StatusLine) - .await - .to_string(); + let default_str = StatusLine::stock_str(context).await.to_string(); let selfstatus = context .get_config(Config::Selfstatus) .await @@ -345,8 +342,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> { match self.loaded { Loaded::Message { ref chat } => { if self.msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage { - self.context - .stock_str(StockMessage::AcSetupMsgSubject) + AcSetupMsgSubject::stock_str(self.context) .await .into_owned() } else if chat.typ == Chattype::Group { @@ -390,21 +386,14 @@ impl<'a, 'b> MimeFactory<'a, 'b> { .unwrap_or_default(), }; - self.context - .stock_string_repl_str( - StockMessage::SubjectForNewContact, - self_name, - ) + SubjectForNewContact::stock_str(self.context, self_name) .await + .to_string() } } } } - Loaded::MDN { .. } => self - .context - .stock_str(StockMessage::ReadRcpt) - .await - .into_owned(), + Loaded::MDN { .. } => ReadRcpt::stock_str(self.context).await.into_owned(), } } @@ -808,12 +797,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> { unprotected_headers .push(Header::new("Autocrypt-Setup-Message".into(), "v1".into())); - placeholdertext = Some( - self.context - .stock_str(StockMessage::AcSetupMsgBody) - .await - .to_string(), - ); + placeholdertext = Some(AcSetupMsgBody::stock_str(self.context).await.to_string()); } SystemMessage::SecurejoinMessage => { let msg = &self.msg; @@ -1071,17 +1055,11 @@ impl<'a, 'b> MimeFactory<'a, 'b> { .get_int(Param::GuaranteeE2ee) .unwrap_or_default() { - self.context - .stock_str(StockMessage::EncryptedMsg) - .await - .into_owned() + EncryptedMsg::stock_str(self.context).await.into_owned() } else { self.msg.get_summarytext(self.context, 32).await }; - let p2 = self - .context - .stock_string_repl_str(StockMessage::ReadRcptMailBody, p1) - .await; + let p2 = ReadRcptMailBody::stock_str(self.context, p1).await; let message_text = format!("{}\r\n", p2); message = message.child( PartBuilder::new() diff --git a/src/mimeparser.rs b/src/mimeparser.rs index 1567a3aa3..a2436f9e1 100644 --- a/src/mimeparser.rs +++ b/src/mimeparser.rs @@ -3,10 +3,12 @@ use std::future::Future; use std::pin::Pin; use anyhow::{bail, Result}; +use charset::Charset; use deltachat_derive::{FromSql, ToSql}; use lettre_email::mime::{self, Mime}; use mailparse::{addrparse_header, DispositionType, MailHeader, MailHeaderMap, SingleInfo}; use once_cell::sync::Lazy; +use percent_encoding::percent_decode_str; use crate::aheader::Aheader; use crate::blob::BlobObject; @@ -25,9 +27,7 @@ use crate::message; use crate::param::{Param, Params}; use crate::peerstate::Peerstate; use crate::simplify::simplify; -use crate::stock::StockMessage; -use charset::Charset; -use percent_encoding::percent_decode_str; +use crate::stock::CantDecryptMsgBody; /// A parsed MIME message. /// @@ -629,7 +629,7 @@ impl MimeMessage { // we currently do not try to decrypt non-autocrypt messages // at all. If we see an encrypted part, we set // decrypting_failed. - let msg_body = context.stock_str(StockMessage::CantDecryptMsgBody).await; + let msg_body = CantDecryptMsgBody::stock_str(context).await; let txt = format!("[{}]", msg_body); let part = Part { diff --git a/src/peerstate.rs b/src/peerstate.rs index e6bf43c79..f2f9e33e2 100644 --- a/src/peerstate.rs +++ b/src/peerstate.rs @@ -13,7 +13,7 @@ use crate::context::Context; use crate::events::EventType; use crate::key::{DcKey, Fingerprint, SignedPublicKey}; use crate::sql::Sql; -use crate::stock::StockMessage; +use crate::stock::ContactSetupChanged; #[derive(Debug)] pub enum PeerstateKeyType { @@ -281,9 +281,7 @@ impl<'a> Peerstate<'a> { .await .unwrap_or_default(); - let msg = context - .stock_string_repl_str(StockMessage::ContactSetupChanged, self.addr.clone()) - .await; + let msg = ContactSetupChanged::stock_str(context, self.addr.clone()).await; chat::add_info_msg(context, contact_chat_id, msg).await; emit_event!(context, EventType::ChatModified(contact_chat_id)); diff --git a/src/securejoin/mod.rs b/src/securejoin/mod.rs index 7f32d5857..efc11b812 100644 --- a/src/securejoin/mod.rs +++ b/src/securejoin/mod.rs @@ -23,7 +23,7 @@ use crate::param::Param; use crate::peerstate::{Peerstate, PeerstateKeyType, PeerstateVerifiedStatus, ToSave}; use crate::qr::check_qr; use crate::sql; -use crate::stock::StockMessage; +use crate::stock::{ContactNotVerified, ContactVerified}; use crate::token; mod bobstate; @@ -822,10 +822,8 @@ async fn secure_connection_established(context: &Context, contact_chat_id: ChatI } else { "?" }; - let msg = context - .stock_string_repl_str(StockMessage::ContactVerified, addr) - .await; - chat::add_info_msg(context, contact_chat_id, &msg).await; + let msg = ContactVerified::stock_str(context, addr).await; + chat::add_info_msg(context, contact_chat_id, msg).await; emit_event!(context, EventType::ChatModified(contact_chat_id)); info!(context, "StockMessage::ContactVerified posted to 1:1 chat"); } @@ -837,16 +835,15 @@ async fn could_not_establish_secure_connection( ) { let contact_id = chat_id_2_contact_id(context, contact_chat_id).await; let contact = Contact::get_by_id(context, contact_id).await; - let msg = context - .stock_string_repl_str( - StockMessage::ContactNotVerified, - if let Ok(ref contact) = contact { - contact.get_addr() - } else { - "?" - }, - ) - .await; + let msg = ContactNotVerified::stock_str( + context, + if let Ok(ref contact) = contact { + contact.get_addr() + } else { + "?" + }, + ) + .await; chat::add_info_msg(context, contact_chat_id, &msg).await; error!( diff --git a/src/smtp/mod.rs b/src/smtp/mod.rs index 6b0a4f1e3..3959d2f03 100644 --- a/src/smtp/mod.rs +++ b/src/smtp/mod.rs @@ -13,7 +13,7 @@ use crate::events::EventType; use crate::login_param::{dc_build_tls, CertificateChecks, LoginParam, ServerLoginParam}; use crate::oauth2::dc_get_oauth2_access_token; use crate::provider::Socket; -use crate::stock::StockMessage; +use crate::stock::ServerResponse; /// SMTP write and read timeout in seconds. const SMTP_TIMEOUT: u64 = 30; @@ -111,13 +111,13 @@ impl Smtp { ) .await; if let Err(ref err) = res { - let message = context - .stock_string_repl_str2( - StockMessage::ServerResponse, - format!("SMTP {}:{}", lp.smtp.server, lp.smtp.port), - err.to_string(), - ) - .await; + let message = ServerResponse::stock_str( + context, + format!("SMTP {}:{}", lp.smtp.server, lp.smtp.port), + err.to_string(), + ) + .await + .to_string(); context.emit_event(EventType::ErrorNetwork(message)); }; diff --git a/src/sql.rs b/src/sql.rs index dffaff619..3605cdaac 100644 --- a/src/sql.rs +++ b/src/sql.rs @@ -10,22 +10,19 @@ use std::time::Duration; use anyhow::format_err; use rusqlite::{Connection, Error as SqlError, OpenFlags}; -use crate::chat::add_device_msg; +use crate::chat::{add_device_msg, update_device_icon, update_saved_messages_icon}; +use crate::config::Config; use crate::config::Config::DeleteServerAfter; -use crate::constants::{ShowEmails, DC_CHAT_ID_TRASH}; +use crate::constants::{ShowEmails, Viewtype, DC_CHAT_ID_TRASH}; use crate::context::Context; use crate::dc_tools::{dc_delete_file, time, EmailAddress}; use crate::ephemeral::start_ephemeral_timers; use crate::imap; +use crate::message::Message; use crate::param::{Param, Params}; use crate::peerstate::Peerstate; use crate::provider::get_provider_by_domain; -use crate::stock::StockMessage; -use crate::{ - chat::{update_device_icon, update_saved_messages_icon}, - config::Config, -}; -use crate::{constants::Viewtype, message::Message}; +use crate::stock::DeleteServerTurnedOff; #[macro_export] macro_rules! paramsv { @@ -1544,12 +1541,7 @@ CREATE INDEX devmsglabels_index1 ON devmsglabels (label); // So, for people who have delete_server enabled, disable it and add a hint to the devicechat: if context.get_config_delete_server_after().await.is_some() { let mut msg = Message::new(Viewtype::Text); - msg.text = Some( - context - .stock_str(StockMessage::DeleteServerTurnedOff) - .await - .into(), - ); + msg.text = Some(DeleteServerTurnedOff::stock_str(context).await.into()); add_device_msg(context, None, Some(&mut msg)).await?; context.set_config(DeleteServerAfter, Some("0")).await?; } diff --git a/src/stock.rs b/src/stock.rs index f8e655f37..a70259719 100644 --- a/src/stock.rs +++ b/src/stock.rs @@ -6,15 +6,15 @@ use anyhow::{bail, Error}; use strum::EnumProperty; use strum_macros::EnumProperty; +use crate::blob::BlobObject; use crate::chat; use crate::chat::ProtectionStatus; +use crate::config::Config; use crate::constants::{Viewtype, DC_CONTACT_ID_SELF}; use crate::contact::{Contact, Origin}; use crate::context::Context; use crate::message::Message; use crate::param::Param; -use crate::stock::StockMessage::{DeviceMessagesHint, WelcomeMessage}; -use crate::{blob::BlobObject, config::Config}; /// Stock strings /// @@ -263,10 +263,6 @@ pub enum StockMessage { MsgEphemeralTimerWeeks = 96, } -/* -" -*/ - impl StockMessage { /// Default untranslated strings for stock messages. /// @@ -276,6 +272,1129 @@ impl StockMessage { } } +/// Builder for a stock string. +/// +/// See [`NoMessages`] or any other stock string in this module for an example of how to use +/// this. +struct StockString<'a> { + context: &'a Context, +} + +impl<'a> StockString<'a> { + /// Creates a new [`StockString`] builder. + fn new(context: &'a Context) -> Self { + Self { context } + } + + /// Looks up a translation and returns a further builder. + /// + /// This will look up the translation in the [`Context`] if one is registered. It + /// returns a further builder type which can be used to substitute replacement strings + /// or build the final message. + async fn id(self, id: StockMessage) -> TranslatedStockString<'a> { + TranslatedStockString { + context: self.context, + message: self + .context + .translated_stockstrings + .read() + .await + .get(&(id as usize)) + .map(|s| Cow::Owned(s.to_owned())) + .unwrap_or_else(|| Cow::Borrowed(id.fallback())), + } + } +} + +/// Stock string builder which allows retrieval of the message. +/// +/// This builder allows retrieval of the message using [`TranslatedStockString::msg`], if it +/// needs substitutions first however it provides further builder methods. +struct TranslatedStockString<'a> { + context: &'a Context, + message: Cow<'static, str>, +} + +impl<'a> TranslatedStockString<'a> { + /// Retrieves the built message. + fn msg(self) -> Cow<'static, str> { + self.message + } + + /// Substitutes the first replacement value if one is present. + fn replace1(self, replacement: impl AsRef) -> Self { + let msg = self + .message + .as_ref() + .replacen("%1$s", replacement.as_ref(), 1) + .replacen("%1$d", replacement.as_ref(), 1) + .replacen("%1$@", replacement.as_ref(), 1); + Self { + context: self.context, + message: Cow::Owned(msg), + } + } + + /// Substitutes the second replacement value if one is present. + /// + /// Be aware you probably should have also called [`TranslatedStockString::replace1`] if + /// you are calling this. + fn replace2(self, replacement: impl AsRef) -> Self { + let msg = self + .message + .as_ref() + .replacen("%2$s", replacement.as_ref(), 1) + .replacen("%2$d", replacement.as_ref(), 1) + .replacen("%2$@", replacement.as_ref(), 1); + Self { + context: self.context, + message: Cow::Owned(msg), + } + } + + /// Augments the message by saying it was performed by a user. + /// + /// This looks up the display name of `contact` and uses the [`MsgActionByMe`] and + /// [`MsgActionByUser`] stock strings to turn the stock string in one that says the + /// action was performed by this user. + /// + /// E.g. this turns `Group image changed.` into `Group image changed by me.` or `Group + /// image changed by Alice`. + /// + /// Note that the original message should end in a `.`. + async fn action_by_contact(self, contact: u32) -> TranslatedStockString<'a> { + let message = self.message.as_ref().trim_end_matches('.'); + let message = match contact { + DC_CONTACT_ID_SELF => MsgActionByMe::stock_str(self.context, message).await, + _ => { + let displayname = Contact::get_by_id(self.context, contact) + .await + .map(|contact| contact.get_name_n_addr()) + .unwrap_or_else(|_| format!("{}", contact)); + MsgActionByUser::stock_str(self.context, message, displayname).await + } + }; + TranslatedStockString { + context: self.context, + message, + } + } +} + +#[derive(Debug)] +pub(crate) enum NoMessages {} + +impl NoMessages { + /// Stock string: `No messages.`. + pub async fn stock_str(context: &Context) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::NoMessages) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum SelfMsg {} + +impl SelfMsg { + /// Stock string: `Me`. + pub async fn stock_str(context: &Context) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::SelfMsg) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum Draft {} + +impl Draft { + /// Stock string: `Draft`. + pub async fn stock_str(context: &Context) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::Draft) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum VoiceMessage {} + +impl VoiceMessage { + /// Stock string: `Voice message`. + pub async fn stock_str(context: &Context) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::VoiceMessage) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum DeadDrop {} + +impl DeadDrop { + /// Stock string: `Contact requests`. + pub async fn stock_str(context: &Context) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::DeadDrop) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum Image {} + +impl Image { + /// Stock string: `Image`. + pub async fn stock_str(context: &Context) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::Image) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum Video {} + +impl Video { + /// Stock string: `Video`. + pub async fn stock_str(context: &Context) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::Video) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum Audio {} + +impl Audio { + /// Stock string: `Audio`. + pub async fn stock_str(context: &Context) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::Audio) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum File {} + +impl File { + /// Stock string: `File`. + pub async fn stock_str(context: &Context) -> Cow<'static, str> { + StockString::new(context).id(StockMessage::File).await.msg() + } +} + +#[derive(Debug)] +pub(crate) enum StatusLine {} + +impl StatusLine { + /// Stock string: `Sent with my Delta Chat Messenger: https://delta.chat`. + pub async fn stock_str(context: &Context) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::StatusLine) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum NewGroupDraft {} + +impl NewGroupDraft { + /// Stock string: `Hello, I've just created the group "%1$s" for us.`. + pub async fn stock_str(context: &Context, group_name: impl AsRef) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::NewGroupDraft) + .await + .replace1(group_name) + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum MsgGrpName {} + +impl MsgGrpName { + /// Stock string: `Group name changed from "%1$s" to "%2$s".`. + pub async fn stock_str( + context: &Context, + from_group: impl AsRef, + to_group: impl AsRef, + by_contact: u32, + ) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::MsgGrpName) + .await + .replace1(from_group) + .replace2(to_group) + .action_by_contact(by_contact) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum MsgGrpImgChanged {} + +impl MsgGrpImgChanged { + /// Stock string: `Group image changed.`. + pub async fn stock_str(context: &Context, by_contact: u32) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::MsgGrpImgChanged) + .await + .action_by_contact(by_contact) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum MsgAddMember {} + +impl MsgAddMember { + /// Stock string: `Member %1$s added.`. + /// + /// The `added_member_addr` parameter should be an email address and is looked up in the + /// contacts to combine with the display name. + pub async fn stock_str( + context: &Context, + added_member_addr: impl AsRef, + by_contact: u32, + ) -> Cow<'static, str> { + let addr = added_member_addr.as_ref(); + let who = match Contact::lookup_id_by_addr(context, addr, Origin::Unknown).await { + Ok(Some(contact_id)) => Contact::get_by_id(context, contact_id) + .await + .map(|contact| contact.get_name_n_addr()) + .unwrap_or_else(|_| addr.to_string()), + _ => addr.to_string(), + }; + StockString::new(context) + .id(StockMessage::MsgAddMember) + .await + .replace1(who) + .action_by_contact(by_contact) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum MsgDelMember {} + +impl MsgDelMember { + /// Stock string: `Member %1$s removed.`. + /// + /// The `removed_member_addr` parameter should be an email address and is looked up in + /// the contacts to combine with the display name. + pub async fn stock_str( + context: &Context, + removed_member_addr: impl AsRef, + by_contact: u32, + ) -> Cow<'static, str> { + let addr = removed_member_addr.as_ref(); + let who = match Contact::lookup_id_by_addr(context, addr, Origin::Unknown).await { + Ok(Some(contact_id)) => Contact::get_by_id(context, contact_id) + .await + .map(|contact| contact.get_name_n_addr()) + .unwrap_or_else(|_| addr.to_string()), + _ => addr.to_string(), + }; + StockString::new(context) + .id(StockMessage::MsgDelMember) + .await + .replace1(who) + .action_by_contact(by_contact) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum MsgGroupLeft {} + +impl MsgGroupLeft { + /// Stock string: `Group left.`. + pub async fn stock_str(context: &Context, by_contact: u32) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::MsgGroupLeft) + .await + .action_by_contact(by_contact) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum Gif {} + +impl Gif { + /// Stock string: `GIF`. + pub async fn stock_str(context: &Context) -> Cow<'static, str> { + StockString::new(context).id(StockMessage::Gif).await.msg() + } +} + +#[derive(Debug)] +pub(crate) enum EncryptedMsg {} + +impl EncryptedMsg { + /// Stock string: `Encrypted message`. + pub async fn stock_str(context: &Context) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::EncryptedMsg) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum E2eAvailable {} + +impl E2eAvailable { + /// Stock string: `End-to-end encryption available.`. + pub async fn stock_str(context: &Context) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::E2eAvailable) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum EncrNone {} + +impl EncrNone { + /// Stock string: `No encryption.`. + pub async fn stock_str(context: &Context) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::EncrNone) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum CantDecryptMsgBody {} + +impl CantDecryptMsgBody { + /// Stock string: `This message was encrypted for another setup.`. + pub async fn stock_str(context: &Context) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::CantDecryptMsgBody) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum FingerPrints {} + +impl FingerPrints { + /// Stock string: `Fingerprints`. + pub async fn stock_str(context: &Context) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::FingerPrints) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum ReadRcpt {} + +impl ReadRcpt { + /// Stock string: `Return receipt`. + pub async fn stock_str(context: &Context) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::ReadRcpt) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum ReadRcptMailBody {} + +impl ReadRcptMailBody { + /// Stock string: `This is a return receipt for the message "%1$s".`. + pub async fn stock_str(context: &Context, message: impl AsRef) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::ReadRcptMailBody) + .await + .replace1(message) + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum MsgGrpImgDeleted {} + +impl MsgGrpImgDeleted { + /// Stock string: `Group image deleted.`. + pub async fn stock_str(context: &Context, by_contact: u32) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::MsgGrpImgDeleted) + .await + .action_by_contact(by_contact) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum E2ePreferred {} + +impl E2ePreferred { + /// Stock string: `End-to-end encryption preferred.`. + pub async fn stock_str(context: &Context) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::E2ePreferred) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum ContactVerified {} + +impl ContactVerified { + /// Stock string: `%1$s verified.`. + pub async fn stock_str(context: &Context, contact_addr: impl AsRef) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::ContactVerified) + .await + .replace1(contact_addr) + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum ContactNotVerified {} + +impl ContactNotVerified { + /// Stock string: `Cannot verify %1$s`. + pub async fn stock_str(context: &Context, contact_addr: impl AsRef) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::ContactNotVerified) + .await + .replace1(contact_addr) + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum ContactSetupChanged {} + +impl ContactSetupChanged { + /// Stock string: `Changed setup for %1$s`. + pub async fn stock_str(context: &Context, contact_addr: impl AsRef) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::ContactSetupChanged) + .await + .replace1(contact_addr) + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum ArchivedChats {} + +impl ArchivedChats { + /// Stock string: `Archived chats`. + pub async fn stock_str(context: &Context) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::ArchivedChats) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum AcSetupMsgSubject {} + +impl AcSetupMsgSubject { + /// Stock string: `Autocrypt Setup Message`. + pub async fn stock_str(context: &Context) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::AcSetupMsgSubject) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum AcSetupMsgBody {} + +impl AcSetupMsgBody { + /// Stock string: `This is the Autocrypt Setup Message used to transfer...`. + pub async fn stock_str(context: &Context) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::AcSetupMsgBody) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum CannotLogin {} + +impl CannotLogin { + /// Stock string: `Cannot login as \"%1$s\". Please check...`. + pub async fn stock_str(context: &Context, user: impl AsRef) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::CannotLogin) + .await + .replace1(user) + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum ServerResponse {} + +impl ServerResponse { + /// Stock string: `Could not connect to %1$s: %2$s`. + pub async fn stock_str( + context: &Context, + server: impl AsRef, + details: impl AsRef, + ) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::ServerResponse) + .await + .replace1(server) + .replace2(details) + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum MsgActionByUser {} + +impl MsgActionByUser { + /// Stock string: `%1$s by %2$s.`. + pub async fn stock_str( + context: &Context, + action: impl AsRef, + user: impl AsRef, + ) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::MsgActionByUser) + .await + .replace1(action) + .replace2(user) + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum MsgActionByMe {} + +impl MsgActionByMe { + /// Stock string: `%1$s by me.`. + pub async fn stock_str(context: &Context, action: impl AsRef) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::MsgActionByMe) + .await + .replace1(action) + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum MsgLocationEnabled {} + +impl MsgLocationEnabled { + /// Stock string: `Location streaming enabled.`. + pub async fn stock_str(context: &Context) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::MsgLocationEnabled) + .await + .msg() + } + + /// Stock string: `Location streaming enabled.`. + pub async fn stock_str_by(context: &Context, contact: u32) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::MsgLocationEnabled) + .await + .action_by_contact(contact) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum MsgLocationDisabled {} + +impl MsgLocationDisabled { + /// Stock string: `Location streaming disabled.`. + pub async fn stock_str(context: &Context) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::MsgLocationDisabled) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum Location {} + +impl Location { + /// Stock string: `Location`. + pub async fn stock_str(context: &Context) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::Location) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum Sticker {} + +impl Sticker { + /// Stock string: `Sticker`. + pub async fn stock_str(context: &Context) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::Sticker) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum DeviceMessages {} + +impl DeviceMessages { + /// Stock string: `Device messages`. + pub async fn stock_str(context: &Context) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::DeviceMessages) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum SavedMessages {} + +impl SavedMessages { + /// Stock string: `Saved messages`. + pub async fn stock_str(context: &Context) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::SavedMessages) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum DeviceMessagesHint {} + +impl DeviceMessagesHint { + /// Stock string: `Messages in this chat are generated locally by...`. + pub async fn stock_str(context: &Context) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::DeviceMessagesHint) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum WelcomeMessage {} + +impl WelcomeMessage { + /// Stock string: `Welcome to Delta Chat! – ...`. + pub async fn stock_str(context: &Context) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::WelcomeMessage) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum UnknownSenderForChat {} + +impl UnknownSenderForChat { + /// Stock string: `Unknown sender for this chat. See 'info' for more details.`. + pub async fn stock_str(context: &Context) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::UnknownSenderForChat) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum SubjectForNewContact {} + +impl SubjectForNewContact { + /// Stock string: `Message from %1$s`. + // TODO: This can compute `self_name` itself instead of asking the caller to do this. + pub async fn stock_str(context: &Context, self_name: impl AsRef) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::SubjectForNewContact) + .await + .replace1(self_name) + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum FailedSendingTo {} + +impl FailedSendingTo { + /// Stock string: `Failed to send message to %1$s.`. + pub async fn stock_str(context: &Context, name: impl AsRef) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::FailedSendingTo) + .await + .replace1(name) + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum MsgEphemeralTimerDisabled {} + +impl MsgEphemeralTimerDisabled { + /// Stock string: `Message deletion timer is disabled.`. + pub async fn stock_str(context: &Context, by_contact: u32) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::MsgEphemeralTimerDisabled) + .await + .action_by_contact(by_contact) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum MsgEphemeralTimerEnabled {} + +impl MsgEphemeralTimerEnabled { + /// Stock string: `Message deletion timer is set to %1$s s.`. + pub async fn stock_str( + context: &Context, + timer: impl AsRef, + by_contact: u32, + ) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::MsgEphemeralTimerEnabled) + .await + .replace1(timer) + .action_by_contact(by_contact) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum MsgEphemeralTimerMinute {} + +impl MsgEphemeralTimerMinute { + /// Stock string: `Message deletion timer is set to 1 minute.`. + pub async fn stock_str(context: &Context, by_contact: u32) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::MsgEphemeralTimerMinute) + .await + .action_by_contact(by_contact) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum MsgEphemeralTimerHour {} + +impl MsgEphemeralTimerHour { + /// Stock string: `Message deletion timer is set to 1 hour.`. + pub async fn stock_str(context: &Context, by_contact: u32) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::MsgEphemeralTimerHour) + .await + .action_by_contact(by_contact) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum MsgEphemeralTimerDay {} + +impl MsgEphemeralTimerDay { + /// Stock string: `Message deletion timer is set to 1 day.`. + pub async fn stock_str(context: &Context, by_contact: u32) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::MsgEphemeralTimerDay) + .await + .action_by_contact(by_contact) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum MsgEphemeralTimerWeek {} + +impl MsgEphemeralTimerWeek { + /// Stock string: `Message deletion timer is set to 1 week.`. + pub async fn stock_str(context: &Context, by_contact: u32) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::MsgEphemeralTimerWeek) + .await + .action_by_contact(by_contact) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum VideochatInvitation {} + +impl VideochatInvitation { + /// Stock string: `Video chat invitation`. + pub async fn stock_str(context: &Context) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::VideochatInvitation) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum VideochatInviteMsgBody {} + +impl VideochatInviteMsgBody { + /// Stock string: `You are invited to a video chat, click %1$s to join.`. + pub async fn stock_str(context: &Context, url: impl AsRef) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::VideochatInviteMsgBody) + .await + .replace1(url) + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum ConfigurationFailed {} + +impl ConfigurationFailed { + /// Stock string: `Error:\n\n“%1$s”`. + pub async fn stock_str(context: &Context, details: impl AsRef) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::ConfigurationFailed) + .await + .replace1(details) + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum BadTimeMsgBody {} + +impl BadTimeMsgBody { + /// Stock string: `⚠️ Date or time of your device seem to be inaccurate (%1$s)...`. + // TODO: This could compute now itself. + pub async fn stock_str(context: &Context, now: impl AsRef) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::BadTimeMsgBody) + .await + .replace1(now) + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum UpdateReminderMsgBody {} + +impl UpdateReminderMsgBody { + /// Stock string: `⚠️ Your Delta Chat version might be outdated...`. + pub async fn stock_str(context: &Context) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::UpdateReminderMsgBody) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum ErrorNoNetwork {} + +impl ErrorNoNetwork { + /// Stock string: `Could not find your mail server...`. + pub async fn stock_str(context: &Context) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::ErrorNoNetwork) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum ProtectionEnabled {} + +impl ProtectionEnabled { + /// Stock string: `Chat protection enabled.`. + pub async fn stock_str(context: &Context, by_contact: u32) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::ProtectionEnabled) + .await + .action_by_contact(by_contact) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum ProtectionDisabled {} + +impl ProtectionDisabled { + /// Stock string: `Chat protection disabled.`. + pub async fn stock_str(context: &Context, by_contact: u32) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::ProtectionDisabled) + .await + .action_by_contact(by_contact) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum ReplyNoun {} + +impl ReplyNoun { + /// Stock string: `Reply`. + pub async fn stock_str(context: &Context) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::ReplyNoun) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum SelfDeletedMsgBody {} + +impl SelfDeletedMsgBody { + /// Stock string: `You deleted the \"Saved messages\" chat...`. + pub async fn stock_str(context: &Context) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::SelfDeletedMsgBody) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum DeleteServerTurnedOff {} + +impl DeleteServerTurnedOff { + /// Stock string: `⚠️ The "Delete messages from server" feature now also...`. + pub async fn stock_str(context: &Context) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::DeleteServerTurnedOff) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum MsgEphemeralTimerMinutes {} + +impl MsgEphemeralTimerMinutes { + /// Stock string: `Message deletion timer is set to %1$s minutes.`. + pub async fn stock_str( + context: &Context, + minutes: impl AsRef, + by_contact: u32, + ) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::MsgEphemeralTimerMinutes) + .await + .replace1(minutes) + .action_by_contact(by_contact) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum MsgEphemeralTimerHours {} + +impl MsgEphemeralTimerHours { + /// Stock string: `Message deletion timer is set to %1$s hours.`. + pub async fn stock_str( + context: &Context, + hours: impl AsRef, + by_contact: u32, + ) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::MsgEphemeralTimerHours) + .await + .replace1(hours) + .action_by_contact(by_contact) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum MsgEphemeralTimerDays {} + +impl MsgEphemeralTimerDays { + /// Stock string: `Message deletion timer is set to %1$s days.`. + pub async fn stock_str( + context: &Context, + days: impl AsRef, + by_contact: u32, + ) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::MsgEphemeralTimerDays) + .await + .replace1(days) + .action_by_contact(by_contact) + .await + .msg() + } +} + +#[derive(Debug)] +pub(crate) enum MsgEphemeralTimerWeeks {} + +impl MsgEphemeralTimerWeeks { + /// Stock string: `Message deletion timer is set to %1$s weeks.`. + pub async fn stock_str( + context: &Context, + weeks: impl AsRef, + by_contact: u32, + ) -> Cow<'static, str> { + StockString::new(context) + .id(StockMessage::MsgEphemeralTimerWeeks) + .await + .replace1(weeks) + .action_by_contact(by_contact) + .await + .msg() + } +} + impl Context { /// Set the stock string for the [StockMessage]. /// @@ -305,136 +1424,17 @@ impl Context { Ok(()) } - /// Return the stock string for the [StockMessage]. - /// - /// Return a translation (if it was set with set_stock_translation before) - /// or a default (English) string. - pub async fn stock_str(&self, id: StockMessage) -> Cow<'_, str> { - match self - .translated_stockstrings - .read() - .await - .get(&(id as usize)) - { - Some(ref x) => Cow::Owned((*x).to_string()), - None => Cow::Borrowed(id.fallback()), - } - } - - /// Return stock string, replacing placeholders with provided string. - /// - /// This replaces both the *first* `%1$s`, `%1$d` and `%1$@` - /// placeholders with the provided string. - /// (the `%1$@` variant is used on iOS, the other are used on Android and Desktop) - pub async fn stock_string_repl_str(&self, id: StockMessage, insert: impl AsRef) -> String { - self.stock_str(id) - .await - .replacen("%1$s", insert.as_ref(), 1) - .replacen("%1$d", insert.as_ref(), 1) - .replacen("%1$@", insert.as_ref(), 1) - } - - /// Return stock string, replacing placeholders with provided int. - /// - /// Like [Context::stock_string_repl_str] but substitute the placeholders - /// with an integer. - pub async fn stock_string_repl_int(&self, id: StockMessage, insert: i32) -> String { - self.stock_string_repl_str(id, format!("{}", insert).as_str()) - .await - } - - /// Return stock string, replacing 2 placeholders with provided string. - /// - /// This replaces both the *first* `%1$s`, `%1$d` and `%1$@` - /// placeholders with the string in `insert` and does the same for - /// `%2$s`, `%2$d` and `%2$@` for `insert2`. - /// (the `%1$@` variant is used on iOS, the other are used on Android and Desktop) - pub async fn stock_string_repl_str2( + /// Returns a stock message saying that protection status has changed. + pub(crate) async fn stock_protection_msg( &self, - id: StockMessage, - insert: impl AsRef, - insert2: impl AsRef, - ) -> String { - self.stock_str(id) - .await - .replacen("%1$s", insert.as_ref(), 1) - .replacen("%1$d", insert.as_ref(), 1) - .replacen("%1$@", insert.as_ref(), 1) - .replacen("%2$s", insert2.as_ref(), 1) - .replacen("%2$d", insert2.as_ref(), 1) - .replacen("%2$@", insert2.as_ref(), 1) - } - - /// Return some kind of stock message - /// - /// If the `id` is [StockMessage::MsgAddMember] or - /// [StockMessage::MsgDelMember] then `param1` is considered to be the - /// contact address and will be replaced by that contact's display - /// name. - /// - /// If `from_id` is not `0`, any trailing dot is removed from the - /// first stock string created so far. If the `from_id` contact is - /// the user itself, i.e. `DC_CONTACT_ID_SELF` the string is used - /// itself as param to the [StockMessage::MsgActionByMe] stock string - /// resulting in a string like "Member Alice added by me." (for - /// [StockMessage::MsgAddMember] as `id`). If the `from_id` contact - /// is any other user than the contact's display name is looked up and - /// used as the second parameter to [StockMessage::MsgActionByUser] with - /// again the original stock string being used as the first parameter, - /// resulting in a string like "Member Alice added by Bob.". - pub async fn stock_system_msg( - &self, - id: StockMessage, - param1: impl AsRef, - param2: impl AsRef, + protect: ProtectionStatus, from_id: u32, ) -> String { - let insert1 = if matches!(id, StockMessage::MsgAddMember | StockMessage::MsgDelMember) { - match Contact::lookup_id_by_addr(self, param1.as_ref(), Origin::Unknown).await { - Ok(Some(contact_id)) => Contact::get_by_id(self, contact_id) - .await - .map(|contact| contact.get_name_n_addr()) - .unwrap_or_else(|_| param1.as_ref().to_string()), - _ => param1.as_ref().to_string(), - } - } else { - param1.as_ref().to_string() - }; - - let action = self - .stock_string_repl_str2(id, insert1, param2.as_ref().to_string()) - .await; - let action1 = action.trim_end_matches('.'); - match from_id { - 0 => action, - DC_CONTACT_ID_SELF => { - self.stock_string_repl_str(StockMessage::MsgActionByMe, action1) - .await - } - _ => { - let displayname = Contact::get_by_id(self, from_id) - .await - .map(|contact| contact.get_name_n_addr()) - .unwrap_or_default(); - - self.stock_string_repl_str2(StockMessage::MsgActionByUser, action1, &displayname) - .await - } + match protect { + ProtectionStatus::Unprotected => ProtectionEnabled::stock_str(self, from_id).await, + ProtectionStatus::Protected => ProtectionDisabled::stock_str(self, from_id).await, } - } - - /// Returns a stock message saying that protection status has changed. - pub async fn stock_protection_msg(&self, protect: ProtectionStatus, from_id: u32) -> String { - self.stock_system_msg( - match protect { - ProtectionStatus::Protected => StockMessage::ProtectionEnabled, - ProtectionStatus::Unprotected => StockMessage::ProtectionDisabled, - }, - "", - "", - from_id, - ) - .await + .to_string() } pub(crate) async fn update_device_chats(&self) -> Result<(), Error> { @@ -454,7 +1454,7 @@ impl Context { // add welcome-messages. by the label, this is done only once, // if the user has deleted the message or the chat, it is not added again. let mut msg = Message::new(Viewtype::Text); - msg.text = Some(self.stock_str(DeviceMessagesHint).await.to_string()); + msg.text = Some(DeviceMessagesHint::stock_str(self).await.to_string()); chat::add_device_msg(&self, Some("core-about-device-chat"), Some(&mut msg)).await?; let image = include_bytes!("../assets/welcome-image.jpg"); @@ -464,7 +1464,7 @@ impl Context { chat::add_device_msg(&self, Some("core-welcome-image"), Some(&mut msg)).await?; let mut msg = Message::new(Viewtype::Text); - msg.text = Some(self.stock_str(WelcomeMessage).await.to_string()); + msg.text = Some(WelcomeMessage::stock_str(self).await.to_string()); chat::add_device_msg(&self, Some("core-welcome"), Some(&mut msg)).await?; Ok(()) } @@ -498,7 +1498,7 @@ mod tests { t.set_stock_translation(StockMessage::NoMessages, "xyz".to_string()) .await .unwrap(); - assert_eq!(t.stock_str(StockMessage::NoMessages).await, "xyz") + assert_eq!(NoMessages::stock_str(&t).await, "xyz") } #[async_std::test] @@ -519,37 +1519,22 @@ mod tests { #[async_std::test] async fn test_stock_str() { let t = TestContext::new().await; - assert_eq!(t.stock_str(StockMessage::NoMessages).await, "No messages."); + assert_eq!(NoMessages::stock_str(&t).await, "No messages."); } #[async_std::test] async fn test_stock_string_repl_str() { let t = TestContext::new().await; // uses %1$s substitution - assert_eq!( - t.stock_string_repl_str(StockMessage::MsgAddMember, "Foo") - .await, - "Member Foo added." - ); + assert_eq!(ContactVerified::stock_str(&t, "Foo").await, "Foo verified."); // We have no string using %1$d to test... } - #[async_std::test] - async fn test_stock_string_repl_int() { - let t = TestContext::new().await; - assert_eq!( - t.stock_string_repl_int(StockMessage::MsgAddMember, 42) - .await, - "Member 42 added." - ); - } - #[async_std::test] async fn test_stock_string_repl_str2() { let t = TestContext::new().await; assert_eq!( - t.stock_string_repl_str2(StockMessage::ServerResponse, "foo", "bar") - .await, + ServerResponse::stock_str(&t, "foo", "bar").await, "Could not connect to foo: bar" ); } @@ -558,8 +1543,7 @@ mod tests { async fn test_stock_system_msg_simple() { let t = TestContext::new().await; assert_eq!( - t.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0) - .await, + MsgLocationEnabled::stock_str(&t).await, "Location streaming enabled." ) } @@ -568,13 +1552,7 @@ mod tests { async fn test_stock_system_msg_add_member_by_me() { let t = TestContext::new().await; assert_eq!( - t.stock_system_msg( - StockMessage::MsgAddMember, - "alice@example.com", - "", - DC_CONTACT_ID_SELF - ) - .await, + MsgAddMember::stock_str(&t, "alice@example.com", DC_CONTACT_ID_SELF).await, "Member alice@example.com added by me." ) } @@ -586,13 +1564,7 @@ mod tests { .await .expect("failed to create contact"); assert_eq!( - t.stock_system_msg( - StockMessage::MsgAddMember, - "alice@example.com", - "", - DC_CONTACT_ID_SELF - ) - .await, + MsgAddMember::stock_str(&t, "alice@example.com", DC_CONTACT_ID_SELF).await, "Member Alice (alice@example.com) added by me." ); } @@ -609,46 +1581,11 @@ mod tests { .expect("failed to create bob") }; assert_eq!( - t.stock_system_msg( - StockMessage::MsgAddMember, - "alice@example.com", - "", - contact_id, - ) - .await, + MsgAddMember::stock_str(&t, "alice@example.com", contact_id,).await, "Member Alice (alice@example.com) added by Bob (bob@example.com)." ); } - #[async_std::test] - async fn test_stock_system_msg_grp_name() { - let t = TestContext::new().await; - assert_eq!( - t.stock_system_msg( - StockMessage::MsgGrpName, - "Some chat", - "Other chat", - DC_CONTACT_ID_SELF - ) - .await, - "Group name changed from \"Some chat\" to \"Other chat\" by me." - ) - } - - #[async_std::test] - async fn test_stock_system_msg_grp_name_other() { - let t = TestContext::new().await; - let id = Contact::create(&t, "Alice", "alice@example.com") - .await - .expect("failed to create contact"); - - assert_eq!( - t.stock_system_msg(StockMessage::MsgGrpName, "Some chat", "Other chat", id) - .await, - "Group name changed from \"Some chat\" to \"Other chat\" by Alice (alice@example.com)." - ) - } - #[async_std::test] async fn test_update_device_chats() { let t = TestContext::new().await;