mirror of
https://github.com/chatmail/core.git
synced 2026-04-17 21:46:35 +03:00
Use footer as a contact status
This commit is contained in:
@@ -4099,6 +4099,19 @@ char* dc_contact_get_profile_image (const dc_contact_t* contact);
|
|||||||
uint32_t dc_contact_get_color (const dc_contact_t* contact);
|
uint32_t dc_contact_get_color (const dc_contact_t* contact);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the contact's status.
|
||||||
|
*
|
||||||
|
* Status is the last signature received in a message from this contact.
|
||||||
|
*
|
||||||
|
* @memberof dc_contact_t
|
||||||
|
* @param contact The contact object.
|
||||||
|
* @return Contact status, if any.
|
||||||
|
* Empty string otherwise.
|
||||||
|
* Must be released by using dc_str_unref() after usage.
|
||||||
|
*/
|
||||||
|
char* dc_contact_get_status (const dc_contact_t* contact);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a contact is blocked.
|
* Check if a contact is blocked.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -3275,6 +3275,16 @@ pub unsafe extern "C" fn dc_contact_get_color(contact: *mut dc_contact_t) -> u32
|
|||||||
ffi_contact.contact.get_color()
|
ffi_contact.contact.get_color()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn dc_contact_get_status(contact: *mut dc_contact_t) -> *mut libc::c_char {
|
||||||
|
if contact.is_null() {
|
||||||
|
eprintln!("ignoring careless call to dc_contact_get_status()");
|
||||||
|
return "".strdup();
|
||||||
|
}
|
||||||
|
let ffi_contact = &*contact;
|
||||||
|
ffi_contact.contact.get_status().strdup()
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn dc_contact_is_blocked(contact: *mut dc_contact_t) -> libc::c_int {
|
pub unsafe extern "C" fn dc_contact_is_blocked(contact: *mut dc_contact_t) -> libc::c_int {
|
||||||
if contact.is_null() {
|
if contact.is_null() {
|
||||||
|
|||||||
@@ -77,6 +77,14 @@ class Contact(object):
|
|||||||
return None
|
return None
|
||||||
return from_dc_charpointer(dc_res)
|
return from_dc_charpointer(dc_res)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status(self):
|
||||||
|
"""Get contact status.
|
||||||
|
|
||||||
|
:returns: contact status, empty string if it doesn't exist.
|
||||||
|
"""
|
||||||
|
return from_dc_charpointer(lib.dc_contact_get_status(self._dc_contact))
|
||||||
|
|
||||||
def create_chat(self):
|
def create_chat(self):
|
||||||
""" create or get an existing 1:1 chat object for the specified contact or contact id.
|
""" create or get an existing 1:1 chat object for the specified contact or contact id.
|
||||||
|
|
||||||
|
|||||||
@@ -2167,6 +2167,23 @@ class TestOnlineAccount:
|
|||||||
updated_name = update_name()
|
updated_name = update_name()
|
||||||
assert updated_name == "Renamed"
|
assert updated_name == "Renamed"
|
||||||
|
|
||||||
|
def test_status(self, acfactory):
|
||||||
|
"""Test that status is transferred over the network."""
|
||||||
|
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||||
|
|
||||||
|
chat12 = acfactory.get_accepted_chat(ac1, ac2)
|
||||||
|
ac1.set_config("selfstatus", "New status")
|
||||||
|
chat12.send_text("hi")
|
||||||
|
msg = ac2._evtracker.wait_next_incoming_message()
|
||||||
|
assert msg.text == "hi"
|
||||||
|
assert msg.get_sender_contact().status == "New status"
|
||||||
|
|
||||||
|
ac1.set_config("selfstatus", "")
|
||||||
|
chat12.send_text("hello")
|
||||||
|
msg = ac2._evtracker.wait_next_incoming_message()
|
||||||
|
assert msg.text == "hello"
|
||||||
|
assert msg.get_sender_contact().status == ""
|
||||||
|
|
||||||
def test_group_quote(self, acfactory, lp):
|
def test_group_quote(self, acfactory, lp):
|
||||||
"""Test quoting in a group with a new member who have not seen the quoted message."""
|
"""Test quoting in a group with a new member who have not seen the quoted message."""
|
||||||
ac1, ac2, ac3 = accounts = acfactory.get_many_online_accounts(3)
|
ac1, ac2, ac3 = accounts = acfactory.get_many_online_accounts(3)
|
||||||
|
|||||||
@@ -71,6 +71,9 @@ pub struct Contact {
|
|||||||
|
|
||||||
/// Parameters as Param::ProfileImage
|
/// Parameters as Param::ProfileImage
|
||||||
pub param: Params,
|
pub param: Params,
|
||||||
|
|
||||||
|
/// Last seen message signature for this contact, to be displayed in the profile.
|
||||||
|
status: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Possible origins of a contact.
|
/// Possible origins of a contact.
|
||||||
@@ -172,7 +175,7 @@ impl Contact {
|
|||||||
let mut res = context
|
let mut res = context
|
||||||
.sql
|
.sql
|
||||||
.query_row(
|
.query_row(
|
||||||
"SELECT c.name, c.addr, c.origin, c.blocked, c.authname, c.param
|
"SELECT c.name, c.addr, c.origin, c.blocked, c.authname, c.param, c.status
|
||||||
FROM contacts c
|
FROM contacts c
|
||||||
WHERE c.id=?;",
|
WHERE c.id=?;",
|
||||||
paramsv![contact_id as i32],
|
paramsv![contact_id as i32],
|
||||||
@@ -185,6 +188,7 @@ impl Contact {
|
|||||||
blocked: row.get::<_, Option<i32>>(3)?.unwrap_or_default() != 0,
|
blocked: row.get::<_, Option<i32>>(3)?.unwrap_or_default() != 0,
|
||||||
origin: row.get(2)?,
|
origin: row.get(2)?,
|
||||||
param: row.get::<_, String>(5)?.parse().unwrap_or_default(),
|
param: row.get::<_, String>(5)?.parse().unwrap_or_default(),
|
||||||
|
status: row.get(6).unwrap_or_default(),
|
||||||
};
|
};
|
||||||
Ok(contact)
|
Ok(contact)
|
||||||
},
|
},
|
||||||
@@ -196,6 +200,10 @@ impl Contact {
|
|||||||
.get_config(Config::ConfiguredAddr)
|
.get_config(Config::ConfiguredAddr)
|
||||||
.await
|
.await
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
res.status = context
|
||||||
|
.get_config(Config::Selfstatus)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default();
|
||||||
} else if contact_id == DC_CONTACT_ID_DEVICE {
|
} else if contact_id == DC_CONTACT_ID_DEVICE {
|
||||||
res.name = context
|
res.name = context
|
||||||
.stock_str(StockMessage::DeviceMessages)
|
.stock_str(StockMessage::DeviceMessages)
|
||||||
@@ -841,7 +849,8 @@ impl Contact {
|
|||||||
Ok(contact)
|
Ok(contact)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_param(&mut self, context: &Context) -> Result<()> {
|
/// Updates `param` column in the database.
|
||||||
|
pub async fn update_param(&self, context: &Context) -> Result<()> {
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
@@ -852,6 +861,18 @@ impl Contact {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Updates `status` column in the database.
|
||||||
|
pub async fn update_status(&self, context: &Context) -> Result<()> {
|
||||||
|
context
|
||||||
|
.sql
|
||||||
|
.execute(
|
||||||
|
"UPDATE contacts SET status=? WHERE id=?",
|
||||||
|
paramsv![self.status, self.id as i32],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the ID of the contact.
|
/// Get the ID of the contact.
|
||||||
pub fn get_id(&self) -> u32 {
|
pub fn get_id(&self) -> u32 {
|
||||||
self.id
|
self.id
|
||||||
@@ -932,6 +953,13 @@ impl Contact {
|
|||||||
dc_str_to_color(&self.addr)
|
dc_str_to_color(&self.addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the contact's status.
|
||||||
|
///
|
||||||
|
/// Status is the last signature received in a message from this contact.
|
||||||
|
pub fn get_status(&self) -> &str {
|
||||||
|
self.status.as_str()
|
||||||
|
}
|
||||||
|
|
||||||
/// Check if a contact was verified. E.g. by a secure-join QR code scan
|
/// Check if a contact was verified. E.g. by a secure-join QR code scan
|
||||||
/// and if the key has not changed since this verification.
|
/// and if the key has not changed since this verification.
|
||||||
///
|
///
|
||||||
@@ -1163,6 +1191,18 @@ pub(crate) async fn set_profile_image(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets contact status.
|
||||||
|
pub(crate) async fn set_status(context: &Context, contact_id: u32, status: String) -> Result<()> {
|
||||||
|
let mut contact = Contact::load_from_db(context, contact_id).await?;
|
||||||
|
|
||||||
|
if contact.status != status {
|
||||||
|
contact.status = status;
|
||||||
|
contact.update_status(context).await?;
|
||||||
|
context.emit_event(EventType::ContactsChanged(Some(contact_id)));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Normalize a name.
|
/// Normalize a name.
|
||||||
///
|
///
|
||||||
/// - Remove quotes (come from some bad MUA implementations)
|
/// - Remove quotes (come from some bad MUA implementations)
|
||||||
|
|||||||
@@ -237,6 +237,17 @@ pub(crate) async fn dc_receive_imf_inner(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Always update the status, even if there is no footer, to allow removing the status.
|
||||||
|
if let Err(err) = contact::set_status(
|
||||||
|
&context,
|
||||||
|
from_id,
|
||||||
|
mime_parser.footer.clone().unwrap_or_default(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
warn!(context, "cannot update contact status: {}", err);
|
||||||
|
}
|
||||||
|
|
||||||
// Get user-configured server deletion
|
// Get user-configured server deletion
|
||||||
let delete_server_after = context.get_config_delete_server_after().await;
|
let delete_server_after = context.get_config_delete_server_after().await;
|
||||||
|
|
||||||
|
|||||||
@@ -390,10 +390,11 @@ mod tests {
|
|||||||
let input = include_str!("../test-data/message/gmx-quote-body.eml");
|
let input = include_str!("../test-data/message/gmx-quote-body.eml");
|
||||||
let dehtml = dehtml(input).unwrap();
|
let dehtml = dehtml(input).unwrap();
|
||||||
println!("{}", dehtml);
|
println!("{}", dehtml);
|
||||||
let (msg, forwarded, cut, top_quote) = simplify(dehtml, false);
|
let (msg, forwarded, cut, top_quote, footer) = simplify(dehtml, false);
|
||||||
assert_eq!(msg, "Test");
|
assert_eq!(msg, "Test");
|
||||||
assert_eq!(forwarded, false);
|
assert_eq!(forwarded, false);
|
||||||
assert_eq!(cut, false);
|
assert_eq!(cut, false);
|
||||||
assert_eq!(top_quote.as_deref(), Some("test"));
|
assert_eq!(top_quote.as_deref(), Some("test"));
|
||||||
|
assert_eq!(footer, None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,9 @@ pub struct MimeMessage {
|
|||||||
pub(crate) mdn_reports: Vec<Report>,
|
pub(crate) mdn_reports: Vec<Report>,
|
||||||
pub(crate) failure_report: Option<FailureReport>,
|
pub(crate) failure_report: Option<FailureReport>,
|
||||||
|
|
||||||
|
/// Standard USENET signature, if any.
|
||||||
|
pub(crate) footer: Option<String>,
|
||||||
|
|
||||||
// if this flag is set, the parts/text/etc. are just close to the original mime-message;
|
// if this flag is set, the parts/text/etc. are just close to the original mime-message;
|
||||||
// clients should offer a way to view the original message in this case
|
// clients should offer a way to view the original message in this case
|
||||||
pub is_mime_modified: bool,
|
pub is_mime_modified: bool,
|
||||||
@@ -233,6 +236,7 @@ impl MimeMessage {
|
|||||||
user_avatar: None,
|
user_avatar: None,
|
||||||
group_avatar: None,
|
group_avatar: None,
|
||||||
failure_report: None,
|
failure_report: None,
|
||||||
|
footer: None,
|
||||||
is_mime_modified: false,
|
is_mime_modified: false,
|
||||||
decoded_data: Vec::new(),
|
decoded_data: Vec::new(),
|
||||||
};
|
};
|
||||||
@@ -752,9 +756,9 @@ impl MimeMessage {
|
|||||||
|
|
||||||
let mut dehtml_failed = false;
|
let mut dehtml_failed = false;
|
||||||
|
|
||||||
let (simplified_txt, is_forwarded, is_cut, top_quote) =
|
let (simplified_txt, is_forwarded, is_cut, top_quote, footer) =
|
||||||
if decoded_data.is_empty() {
|
if decoded_data.is_empty() {
|
||||||
("".to_string(), false, false, None)
|
("".to_string(), false, false, None, None)
|
||||||
} else {
|
} else {
|
||||||
let is_html = mime_type == mime::TEXT_HTML;
|
let is_html = mime_type == mime::TEXT_HTML;
|
||||||
let out = if is_html {
|
let out = if is_html {
|
||||||
@@ -814,6 +818,8 @@ impl MimeMessage {
|
|||||||
if is_forwarded {
|
if is_forwarded {
|
||||||
self.is_forwarded = true;
|
self.is_forwarded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.footer = footer;
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,16 +17,16 @@ pub fn escape_message_footer_marks(text: &str) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Remove standard (RFC 3676, §4.3) footer if it is found.
|
/// Remove standard (RFC 3676, §4.3) footer if it is found.
|
||||||
/// Returns `(lines, is_footer_removed)` tuple;
|
/// Returns `(lines, footer_lines)` tuple;
|
||||||
/// `is_footer_removed` is set to `true` if the footer was actually removed from `lines`
|
/// `footer_lines` is set to `Some` if the footer was actually removed from `lines`
|
||||||
/// (which is equal to the input array otherwise).
|
/// (which is equal to the input array otherwise).
|
||||||
#[allow(clippy::indexing_slicing)]
|
#[allow(clippy::indexing_slicing)]
|
||||||
fn remove_message_footer<'a>(lines: &'a [&str]) -> (&'a [&'a str], bool) {
|
fn remove_message_footer<'a>(lines: &'a [&str]) -> (&'a [&'a str], Option<&'a [&'a str]>) {
|
||||||
let mut nearly_standard_footer = None;
|
let mut nearly_standard_footer = None;
|
||||||
for (ix, &line) in lines.iter().enumerate() {
|
for (ix, &line) in lines.iter().enumerate() {
|
||||||
match line {
|
match line {
|
||||||
// some providers encode `-- ` to `-- =20` which results in `-- `
|
// some providers encode `-- ` to `-- =20` which results in `-- `
|
||||||
"-- " | "-- " => return (&lines[..ix], true),
|
"-- " | "-- " => return (&lines[..ix], lines.get(ix + 1..)),
|
||||||
// some providers encode `-- ` to `=2D-` which results in only `--`;
|
// some providers encode `-- ` to `=2D-` which results in only `--`;
|
||||||
// use that only when no other footer is found
|
// use that only when no other footer is found
|
||||||
// and if the line before is empty and the line after is not empty
|
// and if the line before is empty and the line after is not empty
|
||||||
@@ -42,9 +42,9 @@ fn remove_message_footer<'a>(lines: &'a [&str]) -> (&'a [&'a str], bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(ix) = nearly_standard_footer {
|
if let Some(ix) = nearly_standard_footer {
|
||||||
return (&lines[..ix], true);
|
return (&lines[..ix], lines.get(ix + 1..));
|
||||||
}
|
}
|
||||||
(lines, false)
|
(lines, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove nonstandard footer and a boolean indicating whether such footer was removed.
|
/// Remove nonstandard footer and a boolean indicating whether such footer was removed.
|
||||||
@@ -73,9 +73,12 @@ pub(crate) fn split_lines(buf: &str) -> Vec<&str> {
|
|||||||
|
|
||||||
/// Simplify message text for chat display.
|
/// Simplify message text for chat display.
|
||||||
/// Remove quotes, signatures, trailing empty lines etc.
|
/// Remove quotes, signatures, trailing empty lines etc.
|
||||||
/// Returns `(text, is_forwarded, is_cut, quote)` tuple,
|
/// Returns `(text, is_forwarded, is_cut, quote, footer)` tuple,
|
||||||
/// returning the simplified text and some additional information gained from the input.
|
/// returning the simplified text and some additional information gained from the input.
|
||||||
pub fn simplify(mut input: String, is_chat_message: bool) -> (String, bool, bool, Option<String>) {
|
pub fn simplify(
|
||||||
|
mut input: String,
|
||||||
|
is_chat_message: bool,
|
||||||
|
) -> (String, bool, bool, Option<String>, Option<String>) {
|
||||||
let mut is_cut = false;
|
let mut is_cut = false;
|
||||||
|
|
||||||
input.retain(|c| c != '\r');
|
input.retain(|c| c != '\r');
|
||||||
@@ -84,8 +87,9 @@ pub fn simplify(mut input: String, is_chat_message: bool) -> (String, bool, bool
|
|||||||
|
|
||||||
let (lines, mut top_quote) = remove_top_quote(lines);
|
let (lines, mut top_quote) = remove_top_quote(lines);
|
||||||
let original_lines = &lines;
|
let original_lines = &lines;
|
||||||
let (lines, footer_removed) = remove_message_footer(lines);
|
let (lines, footer_lines) = remove_message_footer(lines);
|
||||||
is_cut = is_cut || footer_removed;
|
let footer = footer_lines.map(|footer_lines| render_message(footer_lines, false));
|
||||||
|
is_cut = is_cut || footer.is_some();
|
||||||
|
|
||||||
let text = if is_chat_message {
|
let text = if is_chat_message {
|
||||||
render_message(lines, false)
|
render_message(lines, false)
|
||||||
@@ -108,13 +112,13 @@ pub fn simplify(mut input: String, is_chat_message: bool) -> (String, bool, bool
|
|||||||
if !is_chat_message {
|
if !is_chat_message {
|
||||||
top_quote = top_quote.map(|quote| {
|
top_quote = top_quote.map(|quote| {
|
||||||
let quote_lines = split_lines("e);
|
let quote_lines = split_lines("e);
|
||||||
let (quote_lines, footer_removed) = remove_message_footer("e_lines);
|
let (quote_lines, quote_footer_lines) = remove_message_footer("e_lines);
|
||||||
is_cut = is_cut || footer_removed;
|
is_cut = is_cut || quote_footer_lines.is_some();
|
||||||
|
|
||||||
render_message(quote_lines, false)
|
render_message(quote_lines, false)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
(text, is_forwarded, is_cut, top_quote)
|
(text, is_forwarded, is_cut, top_quote, footer)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Skips "forwarded message" header.
|
/// Skips "forwarded message" header.
|
||||||
@@ -269,7 +273,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
// proptest does not support [[:graphical:][:space:]] regex.
|
// proptest does not support [[:graphical:][:space:]] regex.
|
||||||
fn test_simplify_plain_text_fuzzy(input in "[!-~\t \n]+") {
|
fn test_simplify_plain_text_fuzzy(input in "[!-~\t \n]+") {
|
||||||
let (output, _is_forwarded, _, _) = simplify(input, true);
|
let (output, _is_forwarded, _, _, _) = simplify(input, true);
|
||||||
assert!(output.split('\n').all(|s| s != "-- "));
|
assert!(output.split('\n').all(|s| s != "-- "));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,7 +281,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_dont_remove_whole_message() {
|
fn test_dont_remove_whole_message() {
|
||||||
let input = "\n------\nFailed\n------\n\nUh-oh, this workflow did not succeed!\n\nlots of other text".to_string();
|
let input = "\n------\nFailed\n------\n\nUh-oh, this workflow did not succeed!\n\nlots of other text".to_string();
|
||||||
let (plain, is_forwarded, is_cut, _) = simplify(input, false);
|
let (plain, is_forwarded, is_cut, _, _) = simplify(input, false);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
plain,
|
plain,
|
||||||
"------\nFailed\n------\n\nUh-oh, this workflow did not succeed!\n\nlots of other text"
|
"------\nFailed\n------\n\nUh-oh, this workflow did not succeed!\n\nlots of other text"
|
||||||
@@ -289,16 +293,20 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_chat_message() {
|
fn test_chat_message() {
|
||||||
let input = "Hi! How are you?\n\n---\n\nI am good.\n-- \nSent with my Delta Chat Messenger: https://delta.chat".to_string();
|
let input = "Hi! How are you?\n\n---\n\nI am good.\n-- \nSent with my Delta Chat Messenger: https://delta.chat".to_string();
|
||||||
let (plain, is_forwarded, is_cut, _) = simplify(input, true);
|
let (plain, is_forwarded, is_cut, _, footer) = simplify(input, true);
|
||||||
assert_eq!(plain, "Hi! How are you?\n\n---\n\nI am good.");
|
assert_eq!(plain, "Hi! How are you?\n\n---\n\nI am good.");
|
||||||
assert!(!is_forwarded);
|
assert!(!is_forwarded);
|
||||||
assert!(is_cut);
|
assert!(is_cut);
|
||||||
|
assert_eq!(
|
||||||
|
footer.unwrap(),
|
||||||
|
"Sent with my Delta Chat Messenger: https://delta.chat"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_simplify_trim() {
|
fn test_simplify_trim() {
|
||||||
let input = "line1\n\r\r\rline2".to_string();
|
let input = "line1\n\r\r\rline2".to_string();
|
||||||
let (plain, is_forwarded, is_cut, _) = simplify(input, false);
|
let (plain, is_forwarded, is_cut, _, _) = simplify(input, false);
|
||||||
|
|
||||||
assert_eq!(plain, "line1\nline2");
|
assert_eq!(plain, "line1\nline2");
|
||||||
assert!(!is_forwarded);
|
assert!(!is_forwarded);
|
||||||
@@ -308,11 +316,12 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_simplify_forwarded_message() {
|
fn test_simplify_forwarded_message() {
|
||||||
let input = "---------- Forwarded message ----------\r\nFrom: test@example.com\r\n\r\nForwarded message\r\n-- \r\nSignature goes here".to_string();
|
let input = "---------- Forwarded message ----------\r\nFrom: test@example.com\r\n\r\nForwarded message\r\n-- \r\nSignature goes here".to_string();
|
||||||
let (plain, is_forwarded, is_cut, _) = simplify(input, false);
|
let (plain, is_forwarded, is_cut, _, footer) = simplify(input, false);
|
||||||
|
|
||||||
assert_eq!(plain, "Forwarded message");
|
assert_eq!(plain, "Forwarded message");
|
||||||
assert!(is_forwarded);
|
assert!(is_forwarded);
|
||||||
assert!(is_cut);
|
assert!(is_cut);
|
||||||
|
assert_eq!(footer.unwrap(), "Signature goes here");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -354,50 +363,60 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_remove_message_footer() {
|
fn test_remove_message_footer() {
|
||||||
let input = "text\n--\nno footer".to_string();
|
let input = "text\n--\nno footer".to_string();
|
||||||
let (plain, _, is_cut, _) = simplify(input, true);
|
let (plain, _, is_cut, _, footer) = simplify(input, true);
|
||||||
assert_eq!(plain, "text\n--\nno footer");
|
assert_eq!(plain, "text\n--\nno footer");
|
||||||
|
assert_eq!(footer, None);
|
||||||
assert!(!is_cut);
|
assert!(!is_cut);
|
||||||
|
|
||||||
let input = "text\n\n--\n\nno footer".to_string();
|
let input = "text\n\n--\n\nno footer".to_string();
|
||||||
let (plain, _, is_cut, _) = simplify(input, true);
|
let (plain, _, is_cut, _, footer) = simplify(input, true);
|
||||||
assert_eq!(plain, "text\n\n--\n\nno footer");
|
assert_eq!(plain, "text\n\n--\n\nno footer");
|
||||||
|
assert_eq!(footer, None);
|
||||||
assert!(!is_cut);
|
assert!(!is_cut);
|
||||||
|
|
||||||
let input = "text\n\n-- no footer\n\n".to_string();
|
let input = "text\n\n-- no footer\n\n".to_string();
|
||||||
let (plain, _, _, _) = simplify(input, true);
|
let (plain, _, _, _, footer) = simplify(input, true);
|
||||||
assert_eq!(plain, "text\n\n-- no footer");
|
assert_eq!(plain, "text\n\n-- no footer");
|
||||||
|
assert_eq!(footer, None);
|
||||||
|
|
||||||
let input = "text\n\n--\nno footer\n-- \nfooter".to_string();
|
let input = "text\n\n--\nno footer\n-- \nfooter".to_string();
|
||||||
let (plain, _, is_cut, _) = simplify(input, true);
|
let (plain, _, is_cut, _, footer) = simplify(input, true);
|
||||||
assert_eq!(plain, "text\n\n--\nno footer");
|
assert_eq!(plain, "text\n\n--\nno footer");
|
||||||
assert!(is_cut);
|
assert!(is_cut);
|
||||||
|
assert_eq!(footer.unwrap(), "footer");
|
||||||
|
|
||||||
let input = "text\n\n--\ntreated as footer when unescaped".to_string();
|
let input = "text\n\n--\ntreated as footer when unescaped".to_string();
|
||||||
let (plain, _, is_cut, _) = simplify(input.clone(), true);
|
let (plain, _, is_cut, _, footer) = simplify(input.clone(), true);
|
||||||
assert_eq!(plain, "text"); // see remove_message_footer() for some explanations
|
assert_eq!(plain, "text"); // see remove_message_footer() for some explanations
|
||||||
assert!(is_cut);
|
assert!(is_cut);
|
||||||
|
assert_eq!(footer.unwrap(), "treated as footer when unescaped");
|
||||||
let escaped = escape_message_footer_marks(&input);
|
let escaped = escape_message_footer_marks(&input);
|
||||||
let (plain, _, is_cut, _) = simplify(escaped, true);
|
let (plain, _, is_cut, _, footer) = simplify(escaped, true);
|
||||||
assert_eq!(plain, "text\n\n--\ntreated as footer when unescaped");
|
assert_eq!(plain, "text\n\n--\ntreated as footer when unescaped");
|
||||||
assert!(!is_cut);
|
assert!(!is_cut);
|
||||||
|
assert_eq!(footer, None);
|
||||||
|
|
||||||
// Nonstandard footer sent by https://siju.es/
|
// Nonstandard footer sent by https://siju.es/
|
||||||
let input = "Message text here\n---Desde mi teléfono con SIJÚ\n\nQuote here".to_string();
|
let input = "Message text here\n---Desde mi teléfono con SIJÚ\n\nQuote here".to_string();
|
||||||
let (plain, _, is_cut, _) = simplify(input.clone(), false);
|
let (plain, _, is_cut, _, footer) = simplify(input.clone(), false);
|
||||||
assert_eq!(plain, "Message text here [...]");
|
assert_eq!(plain, "Message text here [...]");
|
||||||
assert!(is_cut);
|
assert!(is_cut);
|
||||||
let (plain, _, is_cut, _) = simplify(input.clone(), true);
|
assert_eq!(footer, None);
|
||||||
|
let (plain, _, is_cut, _, footer) = simplify(input.clone(), true);
|
||||||
assert_eq!(plain, input);
|
assert_eq!(plain, input);
|
||||||
assert!(!is_cut);
|
assert!(!is_cut);
|
||||||
|
assert_eq!(footer, None);
|
||||||
|
|
||||||
let input = "--\ntreated as footer when unescaped".to_string();
|
let input = "--\ntreated as footer when unescaped".to_string();
|
||||||
let (plain, _, is_cut, _) = simplify(input.clone(), true);
|
let (plain, _, is_cut, _, footer) = simplify(input.clone(), true);
|
||||||
assert_eq!(plain, ""); // see remove_message_footer() for some explanations
|
assert_eq!(plain, ""); // see remove_message_footer() for some explanations
|
||||||
assert!(is_cut);
|
assert!(is_cut);
|
||||||
|
assert_eq!(footer.unwrap(), "treated as footer when unescaped");
|
||||||
|
|
||||||
let escaped = escape_message_footer_marks(&input);
|
let escaped = escape_message_footer_marks(&input);
|
||||||
let (plain, _, is_cut, _) = simplify(escaped, true);
|
let (plain, _, is_cut, _, footer) = simplify(escaped, true);
|
||||||
assert_eq!(plain, "--\ntreated as footer when unescaped");
|
assert_eq!(plain, "--\ntreated as footer when unescaped");
|
||||||
assert!(!is_cut);
|
assert!(!is_cut);
|
||||||
|
assert_eq!(footer, None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1500,6 +1500,15 @@ CREATE INDEX devmsglabels_index1 ON devmsglabels (label);
|
|||||||
.await?;
|
.await?;
|
||||||
sql.set_raw_config_int(context, "dbversion", 74).await?;
|
sql.set_raw_config_int(context, "dbversion", 74).await?;
|
||||||
}
|
}
|
||||||
|
if dbversion < 75 {
|
||||||
|
info!(context, "[migration] v75");
|
||||||
|
sql.execute(
|
||||||
|
"ALTER TABLE contacts ADD COLUMN status TEXT DEFAULT '';",
|
||||||
|
paramsv![],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
sql.set_raw_config_int(context, "dbversion", 75).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)
|
||||||
|
|||||||
Reference in New Issue
Block a user