mirror of
https://github.com/chatmail/core.git
synced 2026-05-09 01:46:30 +03:00
save subject for messages (#2274)
save subject for messages: - new api `dc_msg_get_subject()`, - when quoting, use the subject of the quoted message as the new subject, instead of the last subject in the chat
This commit is contained in:
@@ -5,6 +5,11 @@
|
|||||||
- breaking change: You have to call dc_stop_io()/dc_start_io() before/after EXPORT_BACKUP:
|
- breaking change: You have to call dc_stop_io()/dc_start_io() before/after EXPORT_BACKUP:
|
||||||
fix race condition and db corruption when a message was received during backup #2253
|
fix race condition and db corruption when a message was received during backup #2253
|
||||||
|
|
||||||
|
- save subject for messages:
|
||||||
|
new api `dc_msg_get_subject()`,
|
||||||
|
when quoting, use the subject of the quoted message as the new subject, instead of the
|
||||||
|
last subject in the chat
|
||||||
|
|
||||||
- new apis to get full or html message,
|
- new apis to get full or html message,
|
||||||
`dc_msg_has_html()` and `dc_get_msg_html()` #2125 #2151
|
`dc_msg_has_html()` and `dc_get_msg_html()` #2125 #2151
|
||||||
|
|
||||||
|
|||||||
@@ -1498,6 +1498,8 @@ char* dc_get_msg_info (dc_context_t* context, uint32_t ms
|
|||||||
* this removes the need for the UI
|
* this removes the need for the UI
|
||||||
* to deal with different formatting options of PLAIN-parts.
|
* to deal with different formatting options of PLAIN-parts.
|
||||||
*
|
*
|
||||||
|
* As the title of the full-message-view, you can use the subject (see dc_msg_get_subject()).
|
||||||
|
*
|
||||||
* **Note:** The returned HTML-code may contain scripts,
|
* **Note:** The returned HTML-code may contain scripts,
|
||||||
* external images that may be misused as hidden read-receipts and so on.
|
* external images that may be misused as hidden read-receipts and so on.
|
||||||
* Taking care of these parts
|
* Taking care of these parts
|
||||||
@@ -3344,6 +3346,25 @@ int64_t dc_msg_get_sort_timestamp (const dc_msg_t* msg);
|
|||||||
char* dc_msg_get_text (const dc_msg_t* msg);
|
char* dc_msg_get_text (const dc_msg_t* msg);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the subject of the email.
|
||||||
|
* If there is no subject associated with the message, an empty string is returned.
|
||||||
|
* NULL is never returned.
|
||||||
|
*
|
||||||
|
* You usually don't need this; if the core thinks that the subject might contain important
|
||||||
|
* information, it automatically prepends it to the message text.
|
||||||
|
*
|
||||||
|
* This function was introduced so that you can use the subject as the title for the
|
||||||
|
* full-message-view (see dc_get_msg_html()).
|
||||||
|
*
|
||||||
|
* For outgoing messages, the subject is not stored and an empty string is returned.
|
||||||
|
*
|
||||||
|
* @memberof dc_msg_t
|
||||||
|
* @param msg The message object.
|
||||||
|
* @return The subject. The result must be released using dc_str_unref(). Never returns NULL.
|
||||||
|
*/
|
||||||
|
char* dc_msg_get_subject (const dc_msg_t* msg);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find out full path, file name and extension of the file associated with a
|
* Find out full path, file name and extension of the file associated with a
|
||||||
* message.
|
* message.
|
||||||
|
|||||||
@@ -2698,6 +2698,16 @@ pub unsafe extern "C" fn dc_msg_get_text(msg: *mut dc_msg_t) -> *mut libc::c_cha
|
|||||||
ffi_msg.message.get_text().unwrap_or_default().strdup()
|
ffi_msg.message.get_text().unwrap_or_default().strdup()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn dc_msg_get_subject(msg: *mut dc_msg_t) -> *mut libc::c_char {
|
||||||
|
if msg.is_null() {
|
||||||
|
eprintln!("ignoring careless call to dc_msg_get_subject()");
|
||||||
|
return "".strdup();
|
||||||
|
}
|
||||||
|
let ffi_msg = &*msg;
|
||||||
|
ffi_msg.message.get_subject().strdup()
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn dc_msg_get_file(msg: *mut dc_msg_t) -> *mut libc::c_char {
|
pub unsafe extern "C" fn dc_msg_get_file(msg: *mut dc_msg_t) -> *mut libc::c_char {
|
||||||
if msg.is_null() {
|
if msg.is_null() {
|
||||||
|
|||||||
@@ -1507,7 +1507,6 @@ class TestOnlineAccount:
|
|||||||
messages = chat2.get_messages()
|
messages = chat2.get_messages()
|
||||||
assert len(messages) == 2
|
assert len(messages) == 2
|
||||||
assert messages[0].text == "msg1"
|
assert messages[0].text == "msg1"
|
||||||
lp.sec("dbg file"+messages[1].filename)
|
|
||||||
assert messages[1].filemime == "image/png"
|
assert messages[1].filemime == "image/png"
|
||||||
assert os.stat(messages[1].filename).st_size == os.stat(original_image_path).st_size
|
assert os.stat(messages[1].filename).st_size == os.stat(original_image_path).st_size
|
||||||
ac.set_config("displayname", "new displayname")
|
ac.set_config("displayname", "new displayname")
|
||||||
|
|||||||
@@ -912,10 +912,10 @@ async fn add_parts(
|
|||||||
let mut stmt = conn.prepare_cached(
|
let mut stmt = conn.prepare_cached(
|
||||||
"INSERT INTO msgs \
|
"INSERT INTO msgs \
|
||||||
(rfc724_mid, server_folder, server_uid, chat_id, from_id, to_id, timestamp, \
|
(rfc724_mid, server_folder, server_uid, chat_id, from_id, to_id, timestamp, \
|
||||||
timestamp_sent, timestamp_rcvd, type, state, msgrmsg, txt, txt_raw, param, \
|
timestamp_sent, timestamp_rcvd, type, state, msgrmsg, txt, subject, txt_raw, param, \
|
||||||
bytes, hidden, mime_headers, mime_in_reply_to, mime_references, mime_modified, \
|
bytes, hidden, mime_headers, mime_in_reply_to, mime_references, mime_modified, \
|
||||||
error, ephemeral_timer, ephemeral_timestamp) \
|
error, ephemeral_timer, ephemeral_timestamp) \
|
||||||
VALUES (?,?,?,?,?,?,?, ?,?,?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?);",
|
VALUES (?,?,?,?,?,?,?, ?,?,?,?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?);",
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let is_location_kml = location_kml_is
|
let is_location_kml = location_kml_is
|
||||||
@@ -972,6 +972,7 @@ async fn add_parts(
|
|||||||
state,
|
state,
|
||||||
is_dc_message,
|
is_dc_message,
|
||||||
if trash { "" } else { &part.msg },
|
if trash { "" } else { &part.msg },
|
||||||
|
if trash { "" } else { &subject },
|
||||||
// txt_raw might contain invalid utf8
|
// txt_raw might contain invalid utf8
|
||||||
if trash { "" } else { &txt_raw },
|
if trash { "" } else { &txt_raw },
|
||||||
if trash {
|
if trash {
|
||||||
|
|||||||
@@ -311,7 +311,7 @@ pub(crate) async fn delete_expired_messages(context: &Context) -> Result<bool, E
|
|||||||
// If you change which information is removed here, also change MsgId::trash() and
|
// If you change which information is removed here, also change MsgId::trash() and
|
||||||
// which information dc_receive_imf::add_parts() still adds to the db if the chat_id is TRASH
|
// which information dc_receive_imf::add_parts() still adds to the db if the chat_id is TRASH
|
||||||
"UPDATE msgs \
|
"UPDATE msgs \
|
||||||
SET chat_id=?, txt='', txt_raw='', mime_headers='', from_id=0, to_id=0, param='' \
|
SET chat_id=?, txt='', subject='', txt_raw='', mime_headers='', from_id=0, to_id=0, param='' \
|
||||||
WHERE \
|
WHERE \
|
||||||
ephemeral_timestamp != 0 \
|
ephemeral_timestamp != 0 \
|
||||||
AND ephemeral_timestamp <= ? \
|
AND ephemeral_timestamp <= ? \
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ impl MsgId {
|
|||||||
.execute(
|
.execute(
|
||||||
// If you change which information is removed here, also change delete_expired_messages() and
|
// If you change which information is removed here, also change delete_expired_messages() and
|
||||||
// which information dc_receive_imf::add_parts() still adds to the db if the chat_id is TRASH
|
// which information dc_receive_imf::add_parts() still adds to the db if the chat_id is TRASH
|
||||||
"UPDATE msgs SET chat_id=?, txt='', txt_raw='', mime_headers='', from_id=0, to_id=0, param='' WHERE id=?",
|
"UPDATE msgs SET chat_id=?, txt='', subject='', txt_raw='', mime_headers='', from_id=0, to_id=0, param='' WHERE id=?",
|
||||||
paramsv![chat_id, self],
|
paramsv![chat_id, self],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -307,6 +307,8 @@ pub struct Message {
|
|||||||
pub(crate) ephemeral_timer: EphemeralTimer,
|
pub(crate) ephemeral_timer: EphemeralTimer,
|
||||||
pub(crate) ephemeral_timestamp: i64,
|
pub(crate) ephemeral_timestamp: i64,
|
||||||
pub(crate) text: Option<String>,
|
pub(crate) text: Option<String>,
|
||||||
|
/// The value of the Subject header. Not set for messages that we sent ourselves.
|
||||||
|
pub(crate) subject: String,
|
||||||
pub(crate) rfc724_mid: String,
|
pub(crate) rfc724_mid: String,
|
||||||
pub(crate) in_reply_to: Option<String>,
|
pub(crate) in_reply_to: Option<String>,
|
||||||
pub(crate) server_folder: Option<String>,
|
pub(crate) server_folder: Option<String>,
|
||||||
@@ -356,6 +358,7 @@ impl Message {
|
|||||||
" m.msgrmsg AS msgrmsg,",
|
" m.msgrmsg AS msgrmsg,",
|
||||||
" m.mime_modified AS mime_modified,",
|
" m.mime_modified AS mime_modified,",
|
||||||
" m.txt AS txt,",
|
" m.txt AS txt,",
|
||||||
|
" m.subject AS subject,",
|
||||||
" m.param AS param,",
|
" m.param AS param,",
|
||||||
" m.hidden AS hidden,",
|
" m.hidden AS hidden,",
|
||||||
" m.location_id AS location,",
|
" m.location_id AS location,",
|
||||||
@@ -405,6 +408,7 @@ impl Message {
|
|||||||
is_dc_message: row.get("msgrmsg")?,
|
is_dc_message: row.get("msgrmsg")?,
|
||||||
mime_modified: row.get("mime_modified")?,
|
mime_modified: row.get("mime_modified")?,
|
||||||
text: Some(text),
|
text: Some(text),
|
||||||
|
subject: row.get("subject")?,
|
||||||
param: row.get::<_, String>("param")?.parse().unwrap_or_default(),
|
param: row.get::<_, String>("param")?.parse().unwrap_or_default(),
|
||||||
hidden: row.get("hidden")?,
|
hidden: row.get("hidden")?,
|
||||||
location_id: row.get("location")?,
|
location_id: row.get("location")?,
|
||||||
@@ -550,6 +554,10 @@ impl Message {
|
|||||||
.map(|text| dc_truncate(text, DC_MAX_GET_TEXT_LEN).to_string())
|
.map(|text| dc_truncate(text, DC_MAX_GET_TEXT_LEN).to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_subject(&self) -> &str {
|
||||||
|
&self.subject
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_filename(&self) -> Option<String> {
|
pub fn get_filename(&self) -> Option<String> {
|
||||||
self.param
|
self.param
|
||||||
.get(Param::File)
|
.get(Param::File)
|
||||||
@@ -2150,7 +2158,7 @@ mod tests {
|
|||||||
*mvbox_move,
|
*mvbox_move,
|
||||||
*chat_msg,
|
*chat_msg,
|
||||||
if folder == &"Spam" { "INBOX" } else { folder }, // Never move setup messages, except if they are in "Spam"
|
if folder == &"Spam" { "INBOX" } else { folder }, // Never move setup messages, except if they are in "Spam"
|
||||||
true,
|
false,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
use anyhow::{bail, ensure, format_err, Error};
|
|
||||||
use chrono::TimeZone;
|
|
||||||
use lettre_email::{mime, Address, Header, MimeMultipartType, PartBuilder};
|
|
||||||
|
|
||||||
use crate::blob::BlobObject;
|
use crate::blob::BlobObject;
|
||||||
use crate::chat::{self, Chat};
|
use crate::chat::{self, Chat};
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::constants::{Chattype, Viewtype, DC_FROM_HANDSHAKE};
|
use crate::constants::{Chattype, Viewtype, DC_FROM_HANDSHAKE};
|
||||||
use crate::contact::Contact;
|
use crate::contact::Contact;
|
||||||
use crate::context::{get_version_str, Context};
|
use crate::context::{get_version_str, Context};
|
||||||
|
use crate::dc_tools::IsNoneOrEmpty;
|
||||||
use crate::dc_tools::{
|
use crate::dc_tools::{
|
||||||
dc_create_outgoing_rfc724_mid, dc_create_smeared_timestamp, dc_get_filebytes, time,
|
dc_create_outgoing_rfc724_mid, dc_create_smeared_timestamp, dc_get_filebytes, time,
|
||||||
};
|
};
|
||||||
@@ -22,6 +19,10 @@ use crate::param::Param;
|
|||||||
use crate::peerstate::{Peerstate, PeerstateVerifiedStatus};
|
use crate::peerstate::{Peerstate, PeerstateVerifiedStatus};
|
||||||
use crate::simplify::escape_message_footer_marks;
|
use crate::simplify::escape_message_footer_marks;
|
||||||
use crate::stock_str;
|
use crate::stock_str;
|
||||||
|
use anyhow::Context as _;
|
||||||
|
use anyhow::{bail, ensure, format_err, Error};
|
||||||
|
use chrono::TimeZone;
|
||||||
|
use lettre_email::{mime, Address, Header, MimeMultipartType, PartBuilder};
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
|
||||||
// attachments of 25 mb brutto should work on the majority of providers
|
// attachments of 25 mb brutto should work on the majority of providers
|
||||||
@@ -64,6 +65,7 @@ pub struct MimeFactory<'a> {
|
|||||||
req_mdn: bool,
|
req_mdn: bool,
|
||||||
last_added_location_id: u32,
|
last_added_location_id: u32,
|
||||||
attach_selfavatar: bool,
|
attach_selfavatar: bool,
|
||||||
|
quoted_msg_subject: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Result of rendering a message, ready to be submitted to a send job.
|
/// Result of rendering a message, ready to be submitted to a send job.
|
||||||
@@ -153,7 +155,8 @@ impl<'a> MimeFactory<'a> {
|
|||||||
))
|
))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await?;
|
.await
|
||||||
|
.context("Can't get mime_in_reply_to, mime_references")?;
|
||||||
|
|
||||||
let default_str = stock_str::status_line(context).await;
|
let default_str = stock_str::status_line(context).await;
|
||||||
let factory = MimeFactory {
|
let factory = MimeFactory {
|
||||||
@@ -173,6 +176,7 @@ impl<'a> MimeFactory<'a> {
|
|||||||
req_mdn,
|
req_mdn,
|
||||||
last_added_location_id: 0,
|
last_added_location_id: 0,
|
||||||
attach_selfavatar,
|
attach_selfavatar,
|
||||||
|
quoted_msg_subject: msg.quoted_message(context).await?.map(|m| m.subject),
|
||||||
};
|
};
|
||||||
Ok(factory)
|
Ok(factory)
|
||||||
}
|
}
|
||||||
@@ -217,6 +221,7 @@ impl<'a> MimeFactory<'a> {
|
|||||||
req_mdn: false,
|
req_mdn: false,
|
||||||
last_added_location_id: 0,
|
last_added_location_id: 0,
|
||||||
attach_selfavatar: false,
|
attach_selfavatar: false,
|
||||||
|
quoted_msg_subject: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
@@ -350,54 +355,64 @@ impl<'a> MimeFactory<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn subject_str(&self, context: &Context) -> String {
|
async fn subject_str(&self, context: &Context) -> anyhow::Result<String> {
|
||||||
match self.loaded {
|
Ok(match self.loaded {
|
||||||
Loaded::Message { ref chat } => {
|
Loaded::Message { ref chat } => {
|
||||||
if self.msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage {
|
if self.msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage {
|
||||||
stock_str::ac_setup_msg_subject(context).await
|
return Ok(stock_str::ac_setup_msg_subject(context).await);
|
||||||
} else if chat.typ == Chattype::Group {
|
}
|
||||||
|
|
||||||
|
if chat.typ == Chattype::Group && self.quoted_msg_subject.is_none_or_empty() {
|
||||||
|
// If we have a `quoted_msg_subject`, we use the subject of the quoted message
|
||||||
|
// instead of the group name
|
||||||
let re = if self.in_reply_to.is_empty() {
|
let re = if self.in_reply_to.is_empty() {
|
||||||
""
|
""
|
||||||
} else {
|
} else {
|
||||||
"Re: "
|
"Re: "
|
||||||
};
|
};
|
||||||
format!("{}{}", re, chat.name)
|
return Ok(format!("{}{}", re, chat.name));
|
||||||
} else {
|
}
|
||||||
match chat.param.get(Param::LastSubject) {
|
|
||||||
Some(last_subject) => {
|
|
||||||
let subject_start = if last_subject.starts_with("Chat:") {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
// "Antw:" is the longest abbreviation in
|
|
||||||
// https://en.wikipedia.org/wiki/List_of_email_subject_abbreviations#Abbreviations_in_other_languages,
|
|
||||||
// so look at the first _5_ characters:
|
|
||||||
match last_subject.chars().take(5).position(|c| c == ':') {
|
|
||||||
Some(prefix_end) => prefix_end + 1,
|
|
||||||
None => 0,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
format!(
|
|
||||||
"Re: {}",
|
|
||||||
last_subject
|
|
||||||
.chars()
|
|
||||||
.skip(subject_start)
|
|
||||||
.collect::<String>()
|
|
||||||
.trim()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
let self_name = match context.get_config(Config::Displayname).await {
|
|
||||||
Some(name) => name,
|
|
||||||
None => context.get_config(Config::Addr).await.unwrap_or_default(),
|
|
||||||
};
|
|
||||||
|
|
||||||
stock_str::subject_for_new_contact(context, self_name).await
|
let parent_subject = if self.quoted_msg_subject.is_none_or_empty() {
|
||||||
}
|
chat.param.get(Param::LastSubject)
|
||||||
|
} else {
|
||||||
|
self.quoted_msg_subject.as_deref()
|
||||||
|
};
|
||||||
|
|
||||||
|
match parent_subject {
|
||||||
|
Some(last_subject) => {
|
||||||
|
let subject_start = if last_subject.starts_with("Chat:") {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
// "Antw:" is the longest abbreviation in
|
||||||
|
// https://en.wikipedia.org/wiki/List_of_email_subject_abbreviations#Abbreviations_in_other_languages,
|
||||||
|
// so look at the first _5_ characters:
|
||||||
|
match last_subject.chars().take(5).position(|c| c == ':') {
|
||||||
|
Some(prefix_end) => prefix_end + 1,
|
||||||
|
None => 0,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
format!(
|
||||||
|
"Re: {}",
|
||||||
|
last_subject
|
||||||
|
.chars()
|
||||||
|
.skip(subject_start)
|
||||||
|
.collect::<String>()
|
||||||
|
.trim()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let self_name = match context.get_config(Config::Displayname).await {
|
||||||
|
Some(name) => name,
|
||||||
|
None => context.get_config(Config::Addr).await.unwrap_or_default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
stock_str::subject_for_new_contact(context, self_name).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loaded::MDN { .. } => stock_str::read_rcpt(context).await,
|
Loaded::MDN { .. } => stock_str::read_rcpt(context).await,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn recipients(&self) -> Vec<String> {
|
pub fn recipients(&self) -> Vec<String> {
|
||||||
@@ -478,7 +493,7 @@ impl<'a> MimeFactory<'a> {
|
|||||||
let grpimage = self.grpimage();
|
let grpimage = self.grpimage();
|
||||||
let force_plaintext = self.should_force_plaintext();
|
let force_plaintext = self.should_force_plaintext();
|
||||||
let skip_autocrypt = self.should_skip_autocrypt();
|
let skip_autocrypt = self.should_skip_autocrypt();
|
||||||
let subject_str = self.subject_str(context).await;
|
let subject_str = self.subject_str(context).await?;
|
||||||
let e2ee_guaranteed = self.is_e2ee_guaranteed();
|
let e2ee_guaranteed = self.is_e2ee_guaranteed();
|
||||||
let encrypt_helper = EncryptHelper::new(context).await?;
|
let encrypt_helper = EncryptHelper::new(context).await?;
|
||||||
|
|
||||||
@@ -1283,11 +1298,15 @@ fn maybe_encode_words(words: &str) -> String {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::chatlist::Chatlist;
|
use crate::chat::ChatId;
|
||||||
|
|
||||||
use crate::contact::Origin;
|
use crate::contact::Origin;
|
||||||
use crate::dc_receive_imf::dc_receive_imf;
|
use crate::dc_receive_imf::dc_receive_imf;
|
||||||
use crate::mimeparser::MimeMessage;
|
use crate::mimeparser::MimeMessage;
|
||||||
use crate::test_utils::TestContext;
|
use crate::test_utils::TestContext;
|
||||||
|
use crate::{chatlist::Chatlist, test_utils::get_chat_msg};
|
||||||
|
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_render_email_address() {
|
fn test_render_email_address() {
|
||||||
@@ -1379,8 +1398,8 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_std::test]
|
#[async_std::test]
|
||||||
async fn test_subject() {
|
async fn test_subject_from_mua() {
|
||||||
// 1.: Receive a mail from an MUA or Delta Chat
|
// 1.: Receive a mail from an MUA
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
msg_to_subject_str(
|
msg_to_subject_str(
|
||||||
b"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
|
b"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
|
||||||
@@ -1410,12 +1429,15 @@ mod tests {
|
|||||||
.await,
|
.await,
|
||||||
"Re: Infos: 42"
|
"Re: Infos: 42"
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// 2. Receive a message from Delta Chat when we did not send any messages before
|
#[async_std::test]
|
||||||
|
async fn test_subject_from_dc() {
|
||||||
|
// 2. Receive a message from Delta Chat
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
msg_to_subject_str(
|
msg_to_subject_str(
|
||||||
b"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
|
b"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
|
||||||
From: Charlie <charlie@example.com>\n\
|
From: bob@example.com\n\
|
||||||
To: alice@example.com\n\
|
To: alice@example.com\n\
|
||||||
Subject: Chat: hello\n\
|
Subject: Chat: hello\n\
|
||||||
Chat-Version: 1.0\n\
|
Chat-Version: 1.0\n\
|
||||||
@@ -1427,7 +1449,10 @@ mod tests {
|
|||||||
.await,
|
.await,
|
||||||
"Re: Chat: hello"
|
"Re: Chat: hello"
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_subject_outgoing() {
|
||||||
// 3. Send the first message to a new contact
|
// 3. Send the first message to a new contact
|
||||||
let t = TestContext::new_alice().await;
|
let t = TestContext::new_alice().await;
|
||||||
|
|
||||||
@@ -1438,11 +1463,14 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(first_subject_str(t).await, "Message from Alice");
|
assert_eq!(first_subject_str(t).await, "Message from Alice");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_subject_unicode() {
|
||||||
// 4. Receive messages with unicode characters and make sure that we do not panic (we do not care about the result)
|
// 4. Receive messages with unicode characters and make sure that we do not panic (we do not care about the result)
|
||||||
msg_to_subject_str(
|
msg_to_subject_str(
|
||||||
"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
|
"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
|
||||||
From: Charlie <charlie@example.com>\n\
|
From: bob@example.com\n\
|
||||||
To: alice@example.com\n\
|
To: alice@example.com\n\
|
||||||
Subject: äääää\n\
|
Subject: äääää\n\
|
||||||
Chat-Version: 1.0\n\
|
Chat-Version: 1.0\n\
|
||||||
@@ -1456,7 +1484,7 @@ mod tests {
|
|||||||
|
|
||||||
msg_to_subject_str(
|
msg_to_subject_str(
|
||||||
"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
|
"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
|
||||||
From: Charlie <charlie@example.com>\n\
|
From: bob@example.com\n\
|
||||||
To: alice@example.com\n\
|
To: alice@example.com\n\
|
||||||
Subject: aäääää\n\
|
Subject: aäääää\n\
|
||||||
Chat-Version: 1.0\n\
|
Chat-Version: 1.0\n\
|
||||||
@@ -1467,15 +1495,18 @@ mod tests {
|
|||||||
.as_bytes(),
|
.as_bytes(),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_subject_mdn() {
|
||||||
// 5. Receive an mdn (read receipt) and make sure the mdn's subject is not used
|
// 5. Receive an mdn (read receipt) and make sure the mdn's subject is not used
|
||||||
let t = TestContext::new_alice().await;
|
let t = TestContext::new_alice().await;
|
||||||
dc_receive_imf(
|
dc_receive_imf(
|
||||||
&t,
|
&t,
|
||||||
b"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
|
b"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
|
||||||
From: alice@example.com\n\
|
From: alice@example.com\n\
|
||||||
To: Charlie <charlie@example.com>\n\
|
To: bob@example.com\n\
|
||||||
Subject: Hello, Charlie\n\
|
Subject: Hello, Bob\n\
|
||||||
Chat-Version: 1.0\n\
|
Chat-Version: 1.0\n\
|
||||||
Message-ID: <2893@example.com>\n\
|
Message-ID: <2893@example.com>\n\
|
||||||
Date: Sun, 22 Mar 2020 22:37:56 +0000\n\
|
Date: Sun, 22 Mar 2020 22:37:56 +0000\n\
|
||||||
@@ -1489,7 +1520,7 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let new_msg = incoming_msg_to_reply_msg(
|
let new_msg = incoming_msg_to_reply_msg(
|
||||||
b"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
|
b"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
|
||||||
From: charlie@example.com\n\
|
From: bob@example.com\n\
|
||||||
To: alice@example.com\n\
|
To: alice@example.com\n\
|
||||||
Subject: message opened\n\
|
Subject: message opened\n\
|
||||||
Date: Sun, 22 Mar 2020 23:37:57 +0000\n\
|
Date: Sun, 22 Mar 2020 23:37:57 +0000\n\
|
||||||
@@ -1508,14 +1539,78 @@ mod tests {
|
|||||||
Content-Type: message/disposition-notification\n\
|
Content-Type: message/disposition-notification\n\
|
||||||
\n\
|
\n\
|
||||||
Reporting-UA: Delta Chat 1.28.0\n\
|
Reporting-UA: Delta Chat 1.28.0\n\
|
||||||
Original-Recipient: rfc822;charlie@example.com\n\
|
Original-Recipient: rfc822;bob@example.com\n\
|
||||||
Final-Recipient: rfc822;charlie@example.com\n\
|
Final-Recipient: rfc822;bob@example.com\n\
|
||||||
Original-Message-ID: <2893@example.com>\n\
|
Original-Message-ID: <2893@example.com>\n\
|
||||||
Disposition: manual-action/MDN-sent-automatically; displayed\n\
|
Disposition: manual-action/MDN-sent-automatically; displayed\n\
|
||||||
\n", &t).await;
|
\n", &t).await;
|
||||||
let mf = MimeFactory::from_msg(&t, &new_msg, false).await.unwrap();
|
let mf = MimeFactory::from_msg(&t, &new_msg, false).await.unwrap();
|
||||||
// The subject string should not be "Re: message opened"
|
// The subject string should not be "Re: message opened"
|
||||||
assert_eq!("Re: Hello, Charlie", mf.subject_str(&t).await);
|
assert_eq!("Re: Hello, Bob", mf.subject_str(&t).await.unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_subject_in_group() {
|
||||||
|
// 6. Test that in a group, replies also take the quoted message's subject, while non-replies use the group title as subject
|
||||||
|
let t = TestContext::new_alice().await;
|
||||||
|
let group_id =
|
||||||
|
chat::create_group_chat(&t, chat::ProtectionStatus::Unprotected, "groupname")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let bob = Contact::create(&t, "", "bob@example.org").await.unwrap();
|
||||||
|
chat::add_contact_to_chat(&t, group_id, bob).await;
|
||||||
|
|
||||||
|
async fn send_msg_get_subject(
|
||||||
|
t: &TestContext,
|
||||||
|
group_id: ChatId,
|
||||||
|
quote: Option<&Message>,
|
||||||
|
) -> String {
|
||||||
|
let mut new_msg = Message::new(Viewtype::Text);
|
||||||
|
new_msg.set_text(Some("Hi".to_string()));
|
||||||
|
if let Some(q) = quote {
|
||||||
|
new_msg.set_quote(t, q).await.unwrap();
|
||||||
|
}
|
||||||
|
let sent = t.send_msg(group_id, &mut new_msg).await;
|
||||||
|
t.parse_msg(&sent).await.get_subject().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
let subject = send_msg_get_subject(&t, group_id, None).await;
|
||||||
|
assert_eq!(subject, "groupname");
|
||||||
|
|
||||||
|
let subject = send_msg_get_subject(&t, group_id, None).await;
|
||||||
|
assert_eq!(subject, "Re: groupname");
|
||||||
|
|
||||||
|
dc_receive_imf(
|
||||||
|
&t,
|
||||||
|
format!(
|
||||||
|
"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
|
||||||
|
From: bob@example.com\n\
|
||||||
|
To: alice@example.com\n\
|
||||||
|
Subject: Different subject\n\
|
||||||
|
In-Reply-To: {}\n\
|
||||||
|
Message-ID: <2893@example.com>\n\
|
||||||
|
Date: Sun, 22 Mar 2020 22:37:56 +0000\n\
|
||||||
|
\n\
|
||||||
|
hello\n",
|
||||||
|
t.get_last_msg().await.rfc724_mid
|
||||||
|
)
|
||||||
|
.as_bytes(),
|
||||||
|
"INBOX",
|
||||||
|
5,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let message_from_bob = t.get_last_msg().await;
|
||||||
|
|
||||||
|
let subject = send_msg_get_subject(&t, group_id, None).await;
|
||||||
|
assert_eq!(subject, "Re: groupname");
|
||||||
|
|
||||||
|
let subject = send_msg_get_subject(&t, group_id, Some(&message_from_bob)).await;
|
||||||
|
assert_eq!(subject, "Re: Different subject");
|
||||||
|
|
||||||
|
let subject = send_msg_get_subject(&t, group_id, None).await;
|
||||||
|
assert_eq!(subject, "Re: groupname");
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn first_subject_str(t: TestContext) -> String {
|
async fn first_subject_str(t: TestContext) -> String {
|
||||||
@@ -1534,14 +1629,93 @@ mod tests {
|
|||||||
|
|
||||||
let mf = MimeFactory::from_msg(&t, &new_msg, false).await.unwrap();
|
let mf = MimeFactory::from_msg(&t, &new_msg, false).await.unwrap();
|
||||||
|
|
||||||
mf.subject_str(&t).await
|
mf.subject_str(&t).await.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In `imf_raw`, From has to be bob@example.com, To has to be alice@example.com
|
||||||
async fn msg_to_subject_str(imf_raw: &[u8]) -> String {
|
async fn msg_to_subject_str(imf_raw: &[u8]) -> String {
|
||||||
|
let subject_str = msg_to_subject_str_inner(imf_raw, false, false, false).await;
|
||||||
|
|
||||||
|
// Check that combinations of true and false reproduce the same subject_str:
|
||||||
|
assert_eq!(
|
||||||
|
subject_str,
|
||||||
|
msg_to_subject_str_inner(imf_raw, true, false, false).await
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
subject_str,
|
||||||
|
msg_to_subject_str_inner(imf_raw, false, true, false).await
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
subject_str,
|
||||||
|
msg_to_subject_str_inner(imf_raw, false, true, true).await
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
subject_str,
|
||||||
|
msg_to_subject_str_inner(imf_raw, true, true, false).await
|
||||||
|
);
|
||||||
|
|
||||||
|
// These two combinations are different: If `message_arrives_inbetween` is true, but
|
||||||
|
// `reply` is false, the core is actually expected to use the subject of the message
|
||||||
|
// that arrived inbetween.
|
||||||
|
assert_eq!(
|
||||||
|
"Re: Some other, completely unrelated subject",
|
||||||
|
msg_to_subject_str_inner(imf_raw, false, false, true).await
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
"Re: Some other, completely unrelated subject",
|
||||||
|
msg_to_subject_str_inner(imf_raw, true, false, true).await
|
||||||
|
);
|
||||||
|
|
||||||
|
// We leave away the combination (true, true, true) here:
|
||||||
|
// It would mean that the original message is quoted without sending the quoting message
|
||||||
|
// out yet, then the original message is deleted, then another unrelated message arrives
|
||||||
|
// and then the message with the quote is sent out. Not very realistic.
|
||||||
|
|
||||||
|
subject_str
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn msg_to_subject_str_inner(
|
||||||
|
imf_raw: &[u8],
|
||||||
|
delete_original_msg: bool,
|
||||||
|
reply: bool,
|
||||||
|
message_arrives_inbetween: bool,
|
||||||
|
) -> String {
|
||||||
let t = TestContext::new_alice().await;
|
let t = TestContext::new_alice().await;
|
||||||
let new_msg = incoming_msg_to_reply_msg(imf_raw, &t).await;
|
let mut new_msg = incoming_msg_to_reply_msg(imf_raw, &t).await;
|
||||||
|
let incoming_msg = get_chat_msg(&t, new_msg.chat_id, 0, 2).await;
|
||||||
|
|
||||||
|
if delete_original_msg {
|
||||||
|
incoming_msg.id.delete_from_db(&t).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
if message_arrives_inbetween {
|
||||||
|
dc_receive_imf(
|
||||||
|
&t,
|
||||||
|
b"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
|
||||||
|
From: Bob <bob@example.com>\n\
|
||||||
|
To: alice@example.com\n\
|
||||||
|
Subject: Some other, completely unrelated subject\n\
|
||||||
|
Message-ID: <3cl4@example.com>\n\
|
||||||
|
Date: Sun, 22 Mar 2020 22:37:56 +0000\n\
|
||||||
|
\n\
|
||||||
|
Some other, completely unrelated content\n",
|
||||||
|
"INBOX",
|
||||||
|
2,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let arrived_msg = t.get_last_msg().await;
|
||||||
|
assert_eq!(arrived_msg.chat_id, incoming_msg.chat_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if reply {
|
||||||
|
new_msg.set_quote(&t, &incoming_msg).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
let mf = MimeFactory::from_msg(&t, &new_msg, false).await.unwrap();
|
let mf = MimeFactory::from_msg(&t, &new_msg, false).await.unwrap();
|
||||||
mf.subject_str(&t).await
|
mf.subject_str(&t).await.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a `Message` that replies "Hi" to the incoming email in `imf_raw`.
|
// Creates a `Message` that replies "Hi" to the incoming email in `imf_raw`.
|
||||||
|
|||||||
@@ -126,7 +126,9 @@ pub enum Param {
|
|||||||
/// For Chats
|
/// For Chats
|
||||||
Selftalk = b'K',
|
Selftalk = b'K',
|
||||||
|
|
||||||
/// For Chats: So that on sending a new message we can sent the subject to "Re: <last subject>"
|
/// For Chats: On sending a new message we set the subject to "Re: <last subject>".
|
||||||
|
/// Usually we just use the subject of the parent message, but if the parent message
|
||||||
|
/// is deleted, we use the LastSubject of the chat.
|
||||||
LastSubject = b't',
|
LastSubject = b't',
|
||||||
|
|
||||||
/// For Chats
|
/// For Chats
|
||||||
|
|||||||
@@ -1504,6 +1504,15 @@ CREATE INDEX devmsglabels_index1 ON devmsglabels (label);
|
|||||||
.await?;
|
.await?;
|
||||||
sql.set_raw_config_int(context, "dbversion", 75).await?;
|
sql.set_raw_config_int(context, "dbversion", 75).await?;
|
||||||
}
|
}
|
||||||
|
if dbversion < 76 {
|
||||||
|
info!(context, "[migration] v76");
|
||||||
|
sql.execute(
|
||||||
|
"ALTER TABLE msgs ADD COLUMN subject TEXT DEFAULT '';",
|
||||||
|
paramsv![],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
sql.set_raw_config_int(context, "dbversion", 76).await?;
|
||||||
|
}
|
||||||
|
|
||||||
// (2) updates that require high-level objects
|
// (2) updates that require high-level objects
|
||||||
// (the structure is complete now and all objects are usable)
|
// (the structure is complete now and all objects are usable)
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ use std::{collections::BTreeMap, panic};
|
|||||||
use std::{fmt, thread};
|
use std::{fmt, thread};
|
||||||
|
|
||||||
use ansi_term::Color;
|
use ansi_term::Color;
|
||||||
use async_std::future::Future;
|
|
||||||
use async_std::path::PathBuf;
|
use async_std::path::PathBuf;
|
||||||
use async_std::sync::{Arc, RwLock};
|
use async_std::sync::{Arc, RwLock};
|
||||||
use async_std::{channel, pin::Pin};
|
use async_std::{channel, pin::Pin};
|
||||||
|
use async_std::{future::Future, task};
|
||||||
use chat::ChatItem;
|
use chat::ChatItem;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use tempfile::{tempdir, TempDir};
|
use tempfile::{tempdir, TempDir};
|
||||||
@@ -20,6 +20,7 @@ use tempfile::{tempdir, TempDir};
|
|||||||
use crate::chat::{self, Chat, ChatId};
|
use crate::chat::{self, Chat, ChatId};
|
||||||
use crate::chatlist::Chatlist;
|
use crate::chatlist::Chatlist;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
|
use crate::constants::Chattype;
|
||||||
use crate::constants::{Viewtype, DC_CONTACT_ID_SELF, DC_MSG_ID_DAYMARKER, DC_MSG_ID_MARKER1};
|
use crate::constants::{Viewtype, DC_CONTACT_ID_SELF, DC_MSG_ID_DAYMARKER, DC_MSG_ID_MARKER1};
|
||||||
use crate::contact::{Contact, Origin};
|
use crate::contact::{Contact, Origin};
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
@@ -102,9 +103,12 @@ impl TestContext {
|
|||||||
let (poison_sender, poison_receiver) = channel::bounded(1);
|
let (poison_sender, poison_receiver) = channel::bounded(1);
|
||||||
async_std::task::spawn(async move {
|
async_std::task::spawn(async move {
|
||||||
// Make sure that the test fails if there is a panic on this thread here:
|
// Make sure that the test fails if there is a panic on this thread here:
|
||||||
|
let current_id = task::current().id();
|
||||||
let orig_hook = panic::take_hook();
|
let orig_hook = panic::take_hook();
|
||||||
panic::set_hook(Box::new(move |panic_info| {
|
panic::set_hook(Box::new(move |panic_info| {
|
||||||
poison_sender.try_send(panic_info.to_string()).ok();
|
if task::current().id() == current_id {
|
||||||
|
poison_sender.try_send(panic_info.to_string()).ok();
|
||||||
|
}
|
||||||
orig_hook(panic_info);
|
orig_hook(panic_info);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -310,7 +314,11 @@ impl TestContext {
|
|||||||
/// Gets the most recent message over all chats.
|
/// Gets the most recent message over all chats.
|
||||||
pub async fn get_last_msg(&self) -> Message {
|
pub async fn get_last_msg(&self) -> Message {
|
||||||
let chats = Chatlist::try_load(&self.ctx, 0, None, None).await.unwrap();
|
let chats = Chatlist::try_load(&self.ctx, 0, None, None).await.unwrap();
|
||||||
let msg_id = chats.get_msg_id(chats.len() - 1).unwrap();
|
// 0 is correct in the next line (as opposed to `chats.len() - 1`, which would be the last element):
|
||||||
|
// The chatlist describes what you see when you open DC, a list of chats and in each of them
|
||||||
|
// the first words of the last message. To get the last message overall, we look at the chat at the top of the
|
||||||
|
// list, which has the index 0.
|
||||||
|
let msg_id = chats.get_msg_id(0).unwrap();
|
||||||
Message::load_from_db(&self.ctx, msg_id).await.unwrap()
|
Message::load_from_db(&self.ctx, msg_id).await.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -364,8 +372,17 @@ impl TestContext {
|
|||||||
pub async fn send_text(&self, chat_id: ChatId, txt: &str) -> SentMessage {
|
pub async fn send_text(&self, chat_id: ChatId, txt: &str) -> SentMessage {
|
||||||
let mut msg = Message::new(Viewtype::Text);
|
let mut msg = Message::new(Viewtype::Text);
|
||||||
msg.set_text(Some(txt.to_string()));
|
msg.set_text(Some(txt.to_string()));
|
||||||
chat::prepare_msg(self, chat_id, &mut msg).await.unwrap();
|
self.send_msg(chat_id, &mut msg).await
|
||||||
chat::send_msg(self, chat_id, &mut msg).await.unwrap();
|
}
|
||||||
|
|
||||||
|
/// Sends out the message to the specified chat.
|
||||||
|
///
|
||||||
|
/// This is not hooked up to any SMTP-IMAP pipeline, so the other account must call
|
||||||
|
/// [`TestContext::recv_msg`] with the returned [`SentMessage`] if it wants to receive
|
||||||
|
/// the message.
|
||||||
|
pub async fn send_msg(&self, chat_id: ChatId, msg: &mut Message) -> SentMessage {
|
||||||
|
chat::prepare_msg(self, chat_id, msg).await.unwrap();
|
||||||
|
chat::send_msg(self, chat_id, msg).await.unwrap();
|
||||||
self.pop_sent_msg().await
|
self.pop_sent_msg().await
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,8 +392,9 @@ impl TestContext {
|
|||||||
// This code is mainly the same as `log_msglist` in `cmdline.rs`, so one day, we could
|
// This code is mainly the same as `log_msglist` in `cmdline.rs`, so one day, we could
|
||||||
// merge them to a public function in the `deltachat` crate.
|
// merge them to a public function in the `deltachat` crate.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub async fn print_chat(&self, chat: &Chat) {
|
#[allow(clippy::clippy::indexing_slicing)]
|
||||||
let msglist = chat::get_chat_msgs(self, chat.get_id(), 0x1, None).await;
|
pub async fn print_chat(&self, chat_id: ChatId) {
|
||||||
|
let msglist = chat::get_chat_msgs(self, chat_id, 0x1, None).await;
|
||||||
let msglist: Vec<MsgId> = msglist
|
let msglist: Vec<MsgId> = msglist
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|x| match x {
|
.map(|x| match x {
|
||||||
@@ -386,6 +404,44 @@ impl TestContext {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
let sel_chat = Chat::load_from_db(self, chat_id).await.unwrap();
|
||||||
|
let members = chat::get_chat_contacts(self, sel_chat.id).await;
|
||||||
|
let subtitle = if sel_chat.is_device_talk() {
|
||||||
|
"device-talk".to_string()
|
||||||
|
} else if sel_chat.get_type() == Chattype::Single && !members.is_empty() {
|
||||||
|
let contact = Contact::get_by_id(self, members[0]).await.unwrap();
|
||||||
|
contact.get_addr().to_string()
|
||||||
|
} else if sel_chat.get_type() == Chattype::Mailinglist && !members.is_empty() {
|
||||||
|
"mailinglist".to_string()
|
||||||
|
} else {
|
||||||
|
format!("{} member(s)", members.len())
|
||||||
|
};
|
||||||
|
println!(
|
||||||
|
"{}#{}: {} [{}]{}{}{} {}",
|
||||||
|
sel_chat.typ,
|
||||||
|
sel_chat.get_id(),
|
||||||
|
sel_chat.get_name(),
|
||||||
|
subtitle,
|
||||||
|
if sel_chat.is_muted() { "🔇" } else { "" },
|
||||||
|
if sel_chat.is_sending_locations() {
|
||||||
|
"📍"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
},
|
||||||
|
match sel_chat.get_profile_image(self).await {
|
||||||
|
Some(icon) => match icon.to_str() {
|
||||||
|
Some(icon) => format!(" Icon: {}", icon),
|
||||||
|
_ => " Icon: Err".to_string(),
|
||||||
|
},
|
||||||
|
_ => "".to_string(),
|
||||||
|
},
|
||||||
|
if sel_chat.is_protected() {
|
||||||
|
"🛡️"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
let mut lines_out = 0;
|
let mut lines_out = 0;
|
||||||
for msg_id in msglist {
|
for msg_id in msglist {
|
||||||
if msg_id == MsgId::new(DC_MSG_ID_DAYMARKER) {
|
if msg_id == MsgId::new(DC_MSG_ID_DAYMARKER) {
|
||||||
|
|||||||
Reference in New Issue
Block a user