Introduce summary module

summary::Summary replaces Lot in the Rust API for methods returning
chatlist summaries.  Lot is a legacy type for C API compatibility, so
Summary can be converted into Lot.
This commit is contained in:
link2xt
2021-09-11 17:17:53 +00:00
parent 2a0f6f5cf7
commit 1d1491c95d
7 changed files with 483 additions and 434 deletions

View File

@@ -2404,13 +2404,13 @@ pub unsafe extern "C" fn dc_chatlist_get_summary(
let ctx = &*ffi_list.context;
block_on(async move {
let lot = ffi_list
let summary = ffi_list
.list
.get_summary(ctx, index as usize, maybe_chat)
.await
.log_err(ctx, "get_summary failed")
.unwrap_or_default();
Box::into_raw(Box::new(lot))
Box::into_raw(Box::new(summary.into()))
})
}
@@ -2430,13 +2430,15 @@ pub unsafe extern "C" fn dc_chatlist_get_summary2(
} else {
Some(MsgId::new(msg_id))
};
block_on(async move {
let lot = Chatlist::get_summary2(ctx, ChatId::new(chat_id), msg_id, None)
.await
.log_err(ctx, "get_summary2 failed")
.unwrap_or_default();
Box::into_raw(Box::new(lot))
})
let summary = block_on(Chatlist::get_summary2(
ctx,
ChatId::new(chat_id),
msg_id,
None,
))
.log_err(ctx, "get_summary2 failed")
.unwrap_or_default();
Box::into_raw(Box::new(summary.into()))
}
#[no_mangle]
@@ -2978,10 +2980,10 @@ pub unsafe extern "C" fn dc_msg_get_summary(
let ffi_msg = &mut *msg;
let ctx = &*ffi_msg.context;
block_on(async move {
let lot = ffi_msg.message.get_summary(ctx, maybe_chat).await;
Box::into_raw(Box::new(lot))
})
let summary = block_on(async move { ffi_msg.message.get_summary(ctx, maybe_chat).await })
.log_err(ctx, "dc_msg_get_summary failed")
.unwrap_or_default();
Box::into_raw(Box::new(summary.into()))
}
#[no_mangle]

View File

@@ -16,7 +16,6 @@ use deltachat::dc_tools::*;
use deltachat::imex::*;
use deltachat::location;
use deltachat::log::LogExt;
use deltachat::lot::LotState;
use deltachat::message::{self, Message, MessageState, MsgId};
use deltachat::peerstate::*;
use deltachat::qr::*;
@@ -569,26 +568,25 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
""
},
);
let lot = chatlist.get_summary(&context, i, Some(&chat)).await?;
let summary = chatlist.get_summary(&context, i, Some(&chat)).await?;
let statestr = if chat.visibility == ChatVisibility::Archived {
" [Archived]"
} else {
match lot.get_state() {
LotState::MsgOutPending => " o",
LotState::MsgOutDelivered => "",
LotState::MsgOutMdnRcvd => " √√",
LotState::MsgOutFailed => " !!",
match summary.state {
MessageState::OutPending => " o",
MessageState::OutDelivered => "",
MessageState::OutMdnRcvd => " √√",
MessageState::OutFailed => " !!",
_ => "",
}
};
let timestr = dc_timestamp_to_str(lot.get_timestamp());
let text1 = lot.get_text1();
let text2 = lot.get_text2();
let timestr = dc_timestamp_to_str(summary.timestamp);
println!(
"{}{}{}{} [{}]{}",
text1.unwrap_or(""),
if text1.is_some() { ": " } else { "" },
text2.unwrap_or(""),
"{}{}{} [{}]{}",
summary
.prefix
.map_or_else(String::new, |prefix| format!("{}: ", prefix)),
summary.text,
statestr,
&timestr,
if chat.is_sending_locations() {

View File

@@ -11,9 +11,9 @@ use crate::constants::{
use crate::contact::Contact;
use crate::context::Context;
use crate::ephemeral::delete_expired_messages;
use crate::lot::Lot;
use crate::message::{Message, MessageState, MsgId};
use crate::stock_str;
use crate::summary::Summary;
/// An object representing a single chatlist in memory.
///
@@ -288,26 +288,13 @@ impl Chatlist {
}
}
/// Get a summary for a chatlist index.
///
/// The summary is returned by a dc_lot_t object with the following fields:
///
/// - dc_lot_t::text1: contains the username or the strings "Me", "Draft" and so on.
/// The string may be colored by having a look at text1_meaning.
/// If there is no such name or it should not be displayed, the element is NULL.
/// - dc_lot_t::text1_meaning: one of DC_TEXT1_USERNAME, DC_TEXT1_SELF or DC_TEXT1_DRAFT.
/// Typically used to show dc_lot_t::text1 with different colors. 0 if not applicable.
/// - dc_lot_t::text2: contains an excerpt of the message text or strings as
/// "No messages". May be NULL of there is no such text (eg. for the archive link)
/// - dc_lot_t::timestamp: the timestamp of the message. 0 if not applicable.
/// - dc_lot_t::state: The state of the message as one of the DC_STATE_* constants (see #dc_msg_get_state()).
// 0 if not applicable.
/// Returns a summary for a given chatlist index.
pub async fn get_summary(
&self,
context: &Context,
index: usize,
chat: Option<&Chat>,
) -> Result<Lot> {
) -> Result<Summary> {
// The summary is created by the chat, not by the last message.
// This is because we may want to display drafts here or stuff as
// "is typing".
@@ -320,14 +307,13 @@ impl Chatlist {
Chatlist::get_summary2(context, *chat_id, *lastmsg_id, chat).await
}
/// Returns a summary for a given chatlist item.
pub async fn get_summary2(
context: &Context,
chat_id: ChatId,
lastmsg_id: Option<MsgId>,
chat: Option<&Chat>,
) -> Result<Lot> {
let mut ret = Lot::new();
) -> Result<Summary> {
let chat_loaded: Chat;
let chat = if let Some(chat) = chat {
chat
@@ -344,9 +330,8 @@ impl Chatlist {
} else {
match chat.typ {
Chattype::Group | Chattype::Mailinglist => {
let lastcontact =
Contact::load_from_db(context, lastmsg.from_id).await.ok();
(Some(lastmsg), lastcontact)
let lastcontact = Contact::load_from_db(context, lastmsg.from_id).await?;
(Some(lastmsg), Some(lastcontact))
}
Chattype::Single | Chattype::Undefined => (Some(lastmsg), None),
}
@@ -356,17 +341,15 @@ impl Chatlist {
};
if chat.id.is_archived_link() {
ret.text2 = None;
} else if let Some(mut lastmsg) =
lastmsg.filter(|msg| msg.from_id != DC_CONTACT_ID_UNDEFINED)
{
ret.fill(&mut lastmsg, chat, lastcontact.as_ref(), context)
.await;
Ok(Default::default())
} else if let Some(lastmsg) = lastmsg.filter(|msg| msg.from_id != DC_CONTACT_ID_UNDEFINED) {
Ok(Summary::new(context, &lastmsg, chat, lastcontact.as_ref()).await)
} else {
ret.text2 = Some(stock_str::no_messages(context).await);
Ok(Summary {
text: stock_str::no_messages(context).await,
..Default::default()
})
}
Ok(ret)
}
pub fn get_index_for_id(&self, id: ChatId) -> Option<usize> {
@@ -637,6 +620,6 @@ mod tests {
let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
let summary = chats.get_summary(&t, 0, None).await.unwrap();
assert_eq!(summary.get_text2().unwrap(), "foo: bar test"); // the linebreak should be removed from summary
assert_eq!(summary.text, "foo: bar test"); // the linebreak should be removed from summary
}
}

View File

@@ -89,6 +89,7 @@ mod dehtml;
mod color;
pub mod html;
pub mod plaintext;
pub mod summary;
pub mod dc_receive_imf;
pub mod dc_tools;

View File

@@ -3,6 +3,8 @@
use deltachat_derive::{FromSql, ToSql};
use crate::key::Fingerprint;
use crate::message::MessageState;
use crate::summary::{Summary, SummaryPrefix};
/// An object containing a set of values.
/// The meaning of the values is defined by the function returning the object.
@@ -139,3 +141,40 @@ impl Default for LotState {
LotState::Undefined
}
}
impl From<MessageState> for LotState {
fn from(s: MessageState) -> Self {
use MessageState::*;
match s {
Undefined => LotState::Undefined,
InFresh => LotState::MsgInFresh,
InNoticed => LotState::MsgInNoticed,
InSeen => LotState::MsgInSeen,
OutPreparing => LotState::MsgOutPreparing,
OutDraft => LotState::MsgOutDraft,
OutPending => LotState::MsgOutPending,
OutFailed => LotState::MsgOutFailed,
OutDelivered => LotState::MsgOutDelivered,
OutMdnRcvd => LotState::MsgOutMdnRcvd,
}
}
}
impl From<Summary> for Lot {
fn from(summary: Summary) -> Self {
let (text1, text1_meaning) = match summary.prefix {
None => (None, Meaning::None),
Some(SummaryPrefix::Draft(text)) => (Some(text), Meaning::Text1Draft),
Some(SummaryPrefix::Username(username)) => (Some(username), Meaning::Text1Username),
Some(SummaryPrefix::Me(text)) => (Some(text), Meaning::Text1Self),
};
Self {
text1_meaning,
text1,
text2: Some(summary.text),
timestamp: summary.timestamp,
state: summary.state.into(),
..Default::default()
}
}
}

View File

@@ -6,7 +6,6 @@ use std::convert::TryInto;
use anyhow::{ensure, format_err, Context as _, Result};
use async_std::path::{Path, PathBuf};
use deltachat_derive::{FromSql, ToSql};
use itertools::Itertools;
use rusqlite::types::ValueRef;
use serde::{Deserialize, Serialize};
@@ -26,15 +25,11 @@ use crate::ephemeral::Timer as EphemeralTimer;
use crate::events::EventType;
use crate::job::{self, Action};
use crate::log::LogExt;
use crate::lot::{Lot, LotState, Meaning};
use crate::mimeparser::{parse_message_id, FailureReport, SystemMessage};
use crate::param::{Param, Params};
use crate::pgp::split_armored_data;
use crate::stock_str;
// In practice, the user additionally cuts the string themselves
// pixel-accurate.
const SUMMARY_CHARACTERS: usize = 160;
use crate::summary::{get_summarytext_by_raw, Summary};
/// Message ID, including reserved IDs.
///
@@ -592,23 +587,21 @@ impl Message {
self.ephemeral_timestamp
}
pub async fn get_summary(&mut self, context: &Context, chat: Option<&Chat>) -> Lot {
let mut ret = Lot::new();
/// Returns message summary for display in the search results.
pub async fn get_summary(&mut self, context: &Context, chat: Option<&Chat>) -> Result<Summary> {
let chat_loaded: Chat;
let chat = if let Some(chat) = chat {
chat
} else if let Ok(chat) = Chat::load_from_db(context, self.chat_id).await {
} else {
let chat = Chat::load_from_db(context, self.chat_id).await?;
chat_loaded = chat;
&chat_loaded
} else {
return ret;
};
let contact = if self.from_id != DC_CONTACT_ID_SELF {
match chat.typ {
Chattype::Group | Chattype::Mailinglist => {
Contact::get_by_id(context, self.from_id).await.ok()
Some(Contact::get_by_id(context, self.from_id).await?)
}
Chattype::Single | Chattype::Undefined => None,
}
@@ -616,9 +609,7 @@ impl Message {
None
};
ret.fill(self, chat, contact.as_ref(), context).await;
ret
Ok(Summary::new(context, self, chat, contact.as_ref()).await)
}
pub async fn get_summarytext(&self, context: &Context, approx_characters: usize) -> String {
@@ -1029,24 +1020,6 @@ impl std::fmt::Display for MessageState {
}
}
impl From<MessageState> for LotState {
fn from(s: MessageState) -> Self {
use MessageState::*;
match s {
Undefined => LotState::Undefined,
InFresh => LotState::MsgInFresh,
InNoticed => LotState::MsgInNoticed,
InSeen => LotState::MsgInSeen,
OutPreparing => LotState::MsgOutPreparing,
OutDraft => LotState::MsgOutDraft,
OutPending => LotState::MsgOutPending,
OutFailed => LotState::MsgOutFailed,
OutDelivered => LotState::MsgOutDelivered,
OutMdnRcvd => LotState::MsgOutMdnRcvd,
}
}
}
impl MessageState {
pub fn can_fail(self) -> bool {
use MessageState::*;
@@ -1064,68 +1037,6 @@ impl MessageState {
}
}
impl Lot {
/* library-internal */
/* in practice, the user additionally cuts the string himself pixel-accurate */
pub async fn fill(
&mut self,
msg: &mut Message,
chat: &Chat,
contact: Option<&Contact>,
context: &Context,
) {
if msg.state == MessageState::OutDraft {
self.text1 = Some(stock_str::draft(context).await);
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(stock_str::self_msg(context).await);
self.text1_meaning = Meaning::Text1Self;
}
} else {
match chat.typ {
Chattype::Group | Chattype::Mailinglist => {
if msg.is_info() || contact.is_none() {
self.text1 = None;
self.text1_meaning = Meaning::None;
} else {
self.text1 = msg
.get_override_sender_name()
.or_else(|| contact.map(|contact| msg.get_sender_name(contact)));
self.text1_meaning = Meaning::Text1Username;
}
}
Chattype::Single | Chattype::Undefined => {
self.text1 = None;
self.text1_meaning = Meaning::None;
}
}
}
let mut text2 = get_summarytext_by_raw(
msg.viewtype,
msg.text.as_ref(),
msg.is_forwarded(),
&msg.param,
SUMMARY_CHARACTERS,
context,
)
.await;
if text2.is_empty() && msg.quoted_text().is_some() {
text2 = stock_str::reply_noun(context).await
}
self.text2 = Some(text2);
self.timestamp = msg.get_timestamp();
self.state = msg.state.into();
}
}
pub async fn get_msg_info(context: &Context, msg_id: MsgId) -> Result<String> {
let msg = Message::load_from_db(context, msg_id).await?;
let rawtxt: Option<String> = context
@@ -1491,88 +1402,6 @@ pub async fn update_msg_state(context: &Context, msg_id: MsgId, state: MessageSt
.is_ok()
}
/// Returns a summary text.
pub async fn get_summarytext_by_raw(
viewtype: Viewtype,
text: Option<impl AsRef<str>>,
was_forwarded: bool,
param: &Params,
approx_characters: usize,
context: &Context,
) -> String {
let mut append_text = true;
let prefix = match viewtype {
Viewtype::Image => stock_str::image(context).await,
Viewtype::Gif => stock_str::gif(context).await,
Viewtype::Sticker => stock_str::sticker(context).await,
Viewtype::Video => stock_str::video(context).await,
Viewtype::Voice => stock_str::voice_message(context).await,
Viewtype::Audio | Viewtype::File => {
if param.get_cmd() == SystemMessage::AutocryptSetupMessage {
append_text = false;
stock_str::ac_setup_msg_subject(context).await
} else {
let file_name: String = param
.get_path(Param::File, context)
.unwrap_or(None)
.and_then(|path| {
path.file_name()
.map(|fname| fname.to_string_lossy().into_owned())
})
.unwrap_or_else(|| String::from("ErrFileName"));
let label = if viewtype == Viewtype::Audio {
stock_str::audio(context).await
} else {
stock_str::file(context).await
};
format!("{} {}", label, file_name)
}
}
Viewtype::VideochatInvitation => {
append_text = false;
stock_str::videochat_invitation(context).await
}
_ => {
if param.get_cmd() != SystemMessage::LocationOnly {
"".to_string()
} else {
append_text = false;
stock_str::location(context).await
}
}
};
if !append_text {
return prefix;
}
let summary_content = if let Some(text) = text {
if text.as_ref().is_empty() {
prefix
} else if prefix.is_empty() {
dc_truncate(text.as_ref(), approx_characters).to_string()
} else {
let tmp = format!("{} {}", prefix, text.as_ref());
dc_truncate(&tmp, approx_characters).to_string()
}
} else {
prefix
};
let summary = if was_forwarded {
let tmp = format!(
"{}: {}",
stock_str::forwarded(context).await,
summary_content
);
dc_truncate(&tmp, approx_characters).to_string()
} else {
summary_content
};
summary.split_whitespace().join(" ")
}
// as we do not cut inside words, this results in about 32-42 characters.
// Do not use too long subjects - we add a tag after the subject which gets truncated by the clients otherwise.
// It should also be very clear, the subject is _not_ the whole message.
@@ -2239,203 +2068,6 @@ mod tests {
assert!(chat::prepare_msg(ctx, chat.id, &mut msg).await.is_err());
}
#[async_std::test]
async fn test_get_summarytext_by_raw() {
let d = test::TestContext::new().await;
let ctx = &d.ctx;
let some_text = Some(" bla \t\n\tbla\n\t".to_string());
let empty_text = Some("".to_string());
let no_text: Option<String> = None;
let mut some_file = Params::new();
some_file.set(Param::File, "foo.bar");
assert_eq!(
get_summarytext_by_raw(
Viewtype::Text,
some_text.as_ref(),
false,
&Params::new(),
50,
ctx
)
.await,
"bla bla" // for simple text, the type is not added to the summary
);
assert_eq!(
get_summarytext_by_raw(
Viewtype::Image,
no_text.as_ref(),
false,
&some_file,
50,
ctx
)
.await,
"Image" // file names are not added for images
);
assert_eq!(
get_summarytext_by_raw(
Viewtype::Video,
no_text.as_ref(),
false,
&some_file,
50,
ctx
)
.await,
"Video" // file names are not added for videos
);
assert_eq!(
get_summarytext_by_raw(Viewtype::Gif, no_text.as_ref(), false, &some_file, 50, ctx,)
.await,
"GIF" // file names are not added for GIFs
);
assert_eq!(
get_summarytext_by_raw(
Viewtype::Sticker,
no_text.as_ref(),
false,
&some_file,
50,
ctx,
)
.await,
"Sticker" // file names are not added for stickers
);
assert_eq!(
get_summarytext_by_raw(
Viewtype::Voice,
empty_text.as_ref(),
false,
&some_file,
50,
ctx,
)
.await,
"Voice message" // file names are not added for voice messages, empty text is skipped
);
assert_eq!(
get_summarytext_by_raw(
Viewtype::Voice,
no_text.as_ref(),
false,
&some_file,
50,
ctx
)
.await,
"Voice message" // file names are not added for voice messages
);
assert_eq!(
get_summarytext_by_raw(
Viewtype::Voice,
some_text.as_ref(),
false,
&some_file,
50,
ctx
)
.await,
"Voice message \u{2013} bla bla" // `\u{2013}` explicitly checks for "EN DASH"
);
assert_eq!(
get_summarytext_by_raw(
Viewtype::Audio,
no_text.as_ref(),
false,
&some_file,
50,
ctx
)
.await,
"Audio \u{2013} foo.bar" // file name is added for audio
);
assert_eq!(
get_summarytext_by_raw(
Viewtype::Audio,
empty_text.as_ref(),
false,
&some_file,
50,
ctx,
)
.await,
"Audio \u{2013} foo.bar" // file name is added for audio, empty text is not added
);
assert_eq!(
get_summarytext_by_raw(
Viewtype::Audio,
some_text.as_ref(),
false,
&some_file,
50,
ctx
)
.await,
"Audio \u{2013} foo.bar \u{2013} bla bla" // file name and text added for audio
);
assert_eq!(
get_summarytext_by_raw(
Viewtype::File,
some_text.as_ref(),
false,
&some_file,
50,
ctx
)
.await,
"File \u{2013} foo.bar \u{2013} bla bla" // file name is added for files
);
// Forwarded
assert_eq!(
get_summarytext_by_raw(
Viewtype::Text,
some_text.as_ref(),
true,
&Params::new(),
50,
ctx
)
.await,
"Forwarded: bla bla" // for simple text, the type is not added to the summary
);
assert_eq!(
get_summarytext_by_raw(
Viewtype::File,
some_text.as_ref(),
true,
&some_file,
50,
ctx
)
.await,
"Forwarded: File \u{2013} foo.bar \u{2013} bla bla"
);
let mut asm_file = Params::new();
asm_file.set(Param::File, "foo.bar");
asm_file.set_cmd(SystemMessage::AutocryptSetupMessage);
assert_eq!(
get_summarytext_by_raw(Viewtype::File, no_text.as_ref(), false, &asm_file, 50, ctx)
.await,
"Autocrypt Setup Message" // file name is not added for autocrypt setup messages
);
}
#[async_std::test]
async fn test_parse_webrtc_instance() {
let (webrtc_type, url) = Message::parse_webrtc_instance("basicwebrtc:https://foo/bar");

394
src/summary.rs Normal file
View File

@@ -0,0 +1,394 @@
//! # Message summary for chatlist.
use crate::chat::Chat;
use crate::constants::{Chattype, Viewtype, DC_CONTACT_ID_SELF};
use crate::contact::Contact;
use crate::context::Context;
use crate::dc_tools::dc_truncate;
use crate::message::{Message, MessageState};
use crate::mimeparser::SystemMessage;
use crate::param::{Param, Params};
use crate::stock_str;
use itertools::Itertools;
use std::fmt;
// In practice, the user additionally cuts the string themselves
// pixel-accurate.
const SUMMARY_CHARACTERS: usize = 160;
/// Prefix displayed before message and separated by ":" in the chatlist.
#[derive(Debug)]
pub enum SummaryPrefix {
/// Username.
Username(String),
/// Stock string saying "Draft".
Draft(String),
/// Stock string saying "Me".
Me(String),
}
impl fmt::Display for SummaryPrefix {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
SummaryPrefix::Username(username) => write!(f, "{}", username),
SummaryPrefix::Draft(text) => write!(f, "{}", text),
SummaryPrefix::Me(text) => write!(f, "{}", text),
}
}
}
/// Message summary.
#[derive(Debug, Default)]
pub struct Summary {
/// Part displayed before ":", such as an username or a string "Draft".
pub prefix: Option<SummaryPrefix>,
/// Summary text, always present.
pub text: String,
/// Message timestamp.
pub timestamp: i64,
/// Message state.
pub state: MessageState,
}
impl Summary {
pub async fn new(
context: &Context,
msg: &Message,
chat: &Chat,
contact: Option<&Contact>,
) -> Self {
let prefix = if msg.state == MessageState::OutDraft {
Some(SummaryPrefix::Draft(stock_str::draft(context).await))
} else if msg.from_id == DC_CONTACT_ID_SELF {
if msg.is_info() || chat.is_self_talk() {
None
} else {
Some(SummaryPrefix::Me(stock_str::self_msg(context).await))
}
} else {
match chat.typ {
Chattype::Group | Chattype::Mailinglist => {
if msg.is_info() || contact.is_none() {
None
} else {
msg.get_override_sender_name()
.or_else(|| contact.map(|contact| msg.get_sender_name(contact)))
.map(SummaryPrefix::Username)
}
}
Chattype::Single | Chattype::Undefined => None,
}
};
let mut text = get_summarytext_by_raw(
msg.viewtype,
msg.text.as_ref(),
msg.is_forwarded(),
&msg.param,
SUMMARY_CHARACTERS,
context,
)
.await;
if text.is_empty() && msg.quoted_text().is_some() {
text = stock_str::reply_noun(context).await
}
Self {
prefix,
text,
timestamp: msg.get_timestamp(),
state: msg.state,
}
}
}
/// Returns a summary text.
pub async fn get_summarytext_by_raw(
viewtype: Viewtype,
text: Option<impl AsRef<str>>,
was_forwarded: bool,
param: &Params,
approx_characters: usize,
context: &Context,
) -> String {
let mut append_text = true;
let prefix = match viewtype {
Viewtype::Image => stock_str::image(context).await,
Viewtype::Gif => stock_str::gif(context).await,
Viewtype::Sticker => stock_str::sticker(context).await,
Viewtype::Video => stock_str::video(context).await,
Viewtype::Voice => stock_str::voice_message(context).await,
Viewtype::Audio | Viewtype::File => {
if param.get_cmd() == SystemMessage::AutocryptSetupMessage {
append_text = false;
stock_str::ac_setup_msg_subject(context).await
} else {
let file_name: String = param
.get_path(Param::File, context)
.unwrap_or(None)
.and_then(|path| {
path.file_name()
.map(|fname| fname.to_string_lossy().into_owned())
})
.unwrap_or_else(|| String::from("ErrFileName"));
let label = if viewtype == Viewtype::Audio {
stock_str::audio(context).await
} else {
stock_str::file(context).await
};
format!("{} {}", label, file_name)
}
}
Viewtype::VideochatInvitation => {
append_text = false;
stock_str::videochat_invitation(context).await
}
_ => {
if param.get_cmd() != SystemMessage::LocationOnly {
"".to_string()
} else {
append_text = false;
stock_str::location(context).await
}
}
};
if !append_text {
return prefix;
}
let summary_content = if let Some(text) = text {
if text.as_ref().is_empty() {
prefix
} else if prefix.is_empty() {
dc_truncate(text.as_ref(), approx_characters).to_string()
} else {
let tmp = format!("{} {}", prefix, text.as_ref());
dc_truncate(&tmp, approx_characters).to_string()
}
} else {
prefix
};
let summary = if was_forwarded {
let tmp = format!(
"{}: {}",
stock_str::forwarded(context).await,
summary_content
);
dc_truncate(&tmp, approx_characters).to_string()
} else {
summary_content
};
summary.split_whitespace().join(" ")
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils as test;
#[async_std::test]
async fn test_get_summarytext_by_raw() {
let d = test::TestContext::new().await;
let ctx = &d.ctx;
let some_text = Some(" bla \t\n\tbla\n\t".to_string());
let empty_text = Some("".to_string());
let no_text: Option<String> = None;
let mut some_file = Params::new();
some_file.set(Param::File, "foo.bar");
assert_eq!(
get_summarytext_by_raw(
Viewtype::Text,
some_text.as_ref(),
false,
&Params::new(),
50,
ctx
)
.await,
"bla bla" // for simple text, the type is not added to the summary
);
assert_eq!(
get_summarytext_by_raw(
Viewtype::Image,
no_text.as_ref(),
false,
&some_file,
50,
ctx
)
.await,
"Image" // file names are not added for images
);
assert_eq!(
get_summarytext_by_raw(
Viewtype::Video,
no_text.as_ref(),
false,
&some_file,
50,
ctx
)
.await,
"Video" // file names are not added for videos
);
assert_eq!(
get_summarytext_by_raw(Viewtype::Gif, no_text.as_ref(), false, &some_file, 50, ctx,)
.await,
"GIF" // file names are not added for GIFs
);
assert_eq!(
get_summarytext_by_raw(
Viewtype::Sticker,
no_text.as_ref(),
false,
&some_file,
50,
ctx,
)
.await,
"Sticker" // file names are not added for stickers
);
assert_eq!(
get_summarytext_by_raw(
Viewtype::Voice,
empty_text.as_ref(),
false,
&some_file,
50,
ctx,
)
.await,
"Voice message" // file names are not added for voice messages, empty text is skipped
);
assert_eq!(
get_summarytext_by_raw(
Viewtype::Voice,
no_text.as_ref(),
false,
&some_file,
50,
ctx
)
.await,
"Voice message" // file names are not added for voice messages
);
assert_eq!(
get_summarytext_by_raw(
Viewtype::Voice,
some_text.as_ref(),
false,
&some_file,
50,
ctx
)
.await,
"Voice message \u{2013} bla bla" // `\u{2013}` explicitly checks for "EN DASH"
);
assert_eq!(
get_summarytext_by_raw(
Viewtype::Audio,
no_text.as_ref(),
false,
&some_file,
50,
ctx
)
.await,
"Audio \u{2013} foo.bar" // file name is added for audio
);
assert_eq!(
get_summarytext_by_raw(
Viewtype::Audio,
empty_text.as_ref(),
false,
&some_file,
50,
ctx,
)
.await,
"Audio \u{2013} foo.bar" // file name is added for audio, empty text is not added
);
assert_eq!(
get_summarytext_by_raw(
Viewtype::Audio,
some_text.as_ref(),
false,
&some_file,
50,
ctx
)
.await,
"Audio \u{2013} foo.bar \u{2013} bla bla" // file name and text added for audio
);
assert_eq!(
get_summarytext_by_raw(
Viewtype::File,
some_text.as_ref(),
false,
&some_file,
50,
ctx
)
.await,
"File \u{2013} foo.bar \u{2013} bla bla" // file name is added for files
);
// Forwarded
assert_eq!(
get_summarytext_by_raw(
Viewtype::Text,
some_text.as_ref(),
true,
&Params::new(),
50,
ctx
)
.await,
"Forwarded: bla bla" // for simple text, the type is not added to the summary
);
assert_eq!(
get_summarytext_by_raw(
Viewtype::File,
some_text.as_ref(),
true,
&some_file,
50,
ctx
)
.await,
"Forwarded: File \u{2013} foo.bar \u{2013} bla bla"
);
let mut asm_file = Params::new();
asm_file.set(Param::File, "foo.bar");
asm_file.set_cmd(SystemMessage::AutocryptSetupMessage);
assert_eq!(
get_summarytext_by_raw(Viewtype::File, no_text.as_ref(), false, &asm_file, 50, ctx)
.await,
"Autocrypt Setup Message" // file name is not added for autocrypt setup messages
);
}
}