update chat/contact data only when there was no newer update (#2642)

* check update timestamps for signatures, user-avatars, ephemeral-settings, last-subject

* check update timestamp for group-avatars

* check update timestamp for group-names

* check update timestamp for memberlist

* check update timestamp for protection-settings

* add a more advanced test

* add another more advanced test

* set last-subject-timestamp more carefully

* bubble up errros from set_*timestamp() and check for from_id==0 before

* simplify Params::set_i64()

* remove comment that is more confusing than helpful

* use update_timestamp() wording consistently
This commit is contained in:
bjoern
2021-09-04 22:16:39 +02:00
committed by GitHub
parent d33177a721
commit 3c43d790a3
4 changed files with 367 additions and 89 deletions

View File

@@ -2,7 +2,7 @@
use std::convert::TryFrom;
use anyhow::{bail, ensure, format_err, Result};
use anyhow::{bail, ensure, Result};
use itertools::join;
use mailparse::SingleInfo;
use num_traits::FromPrimitive;
@@ -19,7 +19,7 @@ use crate::constants::{
use crate::contact::{addr_cmp, normalize_name, Contact, Origin, VerifiedStatus};
use crate::context::Context;
use crate::dc_tools::{
dc_create_smeared_timestamp, dc_extract_grpid_from_rfc724_mid, dc_smeared_time, time,
dc_create_smeared_timestamp, dc_extract_grpid_from_rfc724_mid, dc_smeared_time,
};
use crate::ephemeral::{stock_ephemeral_timer_changed, Timer as EphemeralTimer};
use crate::events::EventType;
@@ -210,27 +210,38 @@ pub(crate) async fn dc_receive_imf_inner(
}
if let Some(avatar_action) = &mime_parser.user_avatar {
match contact::set_profile_image(
context,
from_id,
avatar_action,
mime_parser.was_encrypted(),
)
.await
if from_id != 0
&& context
.update_contacts_timestamp(from_id, Param::AvatarTimestamp, sent_timestamp)
.await?
{
Ok(()) => {
context.emit_event(EventType::ChatModified(chat_id));
}
Err(err) => {
warn!(context, "receive_imf cannot update profile image: {}", err);
}
};
match contact::set_profile_image(
context,
from_id,
avatar_action,
mime_parser.was_encrypted(),
)
.await
{
Ok(()) => {
context.emit_event(EventType::ChatModified(chat_id));
}
Err(err) => {
warn!(context, "receive_imf cannot update profile image: {}", err);
}
};
}
}
// Always update the status, even if there is no footer, to allow removing the status.
//
// Ignore MDNs though, as they never contain the signature even if user has set it.
if mime_parser.mdn_reports.is_empty() {
if mime_parser.mdn_reports.is_empty()
&& from_id != 0
&& context
.update_contacts_timestamp(from_id, Param::StatusTimestamp, sent_timestamp)
.await?
{
if let Err(err) = contact::set_status(
context,
from_id,
@@ -410,6 +421,9 @@ async fn add_parts(
}
}
let rcvd_timestamp = dc_smeared_time(context).await;
*sent_timestamp = std::cmp::min(*sent_timestamp, rcvd_timestamp);
// check if the message introduces a new chat:
// - outgoing messages introduce a chat with the first to: address if they are sent by a messenger
// - incoming messages introduce a chat only for known contacts if they are sent by a messenger
@@ -483,6 +497,7 @@ async fn add_parts(
if let Some((new_chat_id, new_chat_id_blocked)) = create_or_lookup_group(
context,
&mut mime_parser,
*sent_timestamp,
if test_normal_chat.is_none() {
allow_creation
} else {
@@ -712,6 +727,7 @@ async fn add_parts(
if let Some((new_chat_id, new_chat_id_blocked)) = create_or_lookup_group(
context,
&mut mime_parser,
*sent_timestamp,
allow_creation,
Blocked::Not,
from_id,
@@ -805,9 +821,8 @@ async fn add_parts(
let location_kml_is = mime_parser.location_kml.is_some();
// correct message_timestamp, it should not be used before,
// however, we cannot do this earlier as we need from_id to be set
// however, we cannot do this earlier as we need chat_id to be set
let in_fresh = state == MessageState::InFresh;
let rcvd_timestamp = time();
let sort_timestamp = calc_sort_timestamp(context, *sent_timestamp, chat_id, in_fresh).await?;
// Apply ephemeral timer changes to the chat.
@@ -823,6 +838,9 @@ async fn add_parts(
|| parent.is_none()
|| parent.unwrap().ephemeral_timer != ephemeral_timer)
&& chat_id.get_ephemeral_timer(context).await? != ephemeral_timer
&& chat_id
.update_timestamp(context, Param::EphemeralSettingsTimestamp, *sent_timestamp)
.await?
{
if let Err(err) = chat_id
.inner_set_ephemeral_timer(context, ephemeral_timer)
@@ -868,6 +886,10 @@ async fn add_parts(
};
if chat.is_protected() || new_status.is_some() {
// TODO: on partial downloads, do not try to check verified properties,
// eg. the Chat-Verified header is in the encrypted part and we would always get warnings.
// however, this seems not to be a big deal as we even show "failed verifications" messages in-chat -
// nothing else is done for "not yet downloaded content".
if let Err(err) = check_verified_properties(context, mime_parser, from_id, to_ids).await
{
warn!(context, "verification problem: {}", err);
@@ -876,15 +898,24 @@ async fn add_parts(
} else {
// change chat protection only when verification check passes
if let Some(new_status) = new_status {
if let Err(e) = chat_id.inner_set_protection(context, new_status).await {
chat::add_info_msg(
if chat_id
.update_timestamp(
context,
chat_id,
format!("Cannot set protection: {}", e),
sort_timestamp,
Param::ProtectionSettingsTimestamp,
*sent_timestamp,
)
.await;
return Ok(chat_id); // do not return an error as this would result in retrying the message
.await?
{
if let Err(e) = chat_id.inner_set_protection(context, new_status).await {
chat::add_info_msg(
context,
chat_id,
format!("Cannot set protection: {}", e),
sort_timestamp,
)
.await;
return Ok(chat_id); // do not return an error as this would result in retrying the message
}
}
set_better_msg(
mime_parser,
@@ -910,8 +941,6 @@ async fn add_parts(
std::cmp::max(sort_timestamp, parent_timestamp)
});
*sent_timestamp = std::cmp::min(*sent_timestamp, rcvd_timestamp);
// if the mime-headers should be saved, find out its size
// (the mime-header ends with an empty line)
let save_mime_headers = context.get_config_bool(Config::SaveMimeHeaders).await?;
@@ -1097,25 +1126,23 @@ INSERT INTO msgs
}
}
async fn update_last_subject(
context: &Context,
chat_id: ChatId,
mime_parser: &MimeMessage,
) -> Result<()> {
let mut chat = Chat::load_from_db(context, chat_id).await?;
chat.param.set(
Param::LastSubject,
mime_parser
.get_subject()
.ok_or_else(|| format_err!("No subject in email"))?,
);
chat.update_param(context).await?;
Ok(())
}
if !is_mdn {
update_last_subject(context, chat_id, mime_parser)
.await
.ok_or_log_msg(context, "Could not update LastSubject of chat");
let mut chat = Chat::load_from_db(context, chat_id).await?;
// In contrast to most other update-timestamps,
// use `sort_timestamp` instead of `sent_timestamp` for the subject-timestamp comparison.
// This way, `LastSubject` actually refers to the most recent message _shown_ in the chat.
if chat
.param
.update_timestamp(Param::SubjectTimestamp, sort_timestamp)?
{
// write the last subject even if empty -
// otherwise a reply may get an outdated subject.
let subject = mime_parser.get_subject().unwrap_or_default();
chat.param.set(Param::LastSubject, subject);
chat.update_param(context).await?;
}
}
Ok(chat_id)
@@ -1314,6 +1341,7 @@ async fn is_probably_private_reply(
async fn create_or_lookup_group(
context: &Context,
mime_parser: &mut MimeMessage,
sent_timestamp: i64,
allow_creation: bool,
create_blocked: Blocked,
from_id: u32,
@@ -1323,7 +1351,6 @@ async fn create_or_lookup_group(
let mut recreate_member_list = false;
let mut send_EVENT_CHAT_MODIFIED = false;
let mut X_MrAddToGrp = None;
let mut X_MrGrpNameChanged = false;
let mut better_msg: String = From::from("");
if mime_parser.is_system_message == SystemMessage::LocationStreamingEnabled {
@@ -1401,7 +1428,6 @@ async fn create_or_lookup_group(
better_msg = stock_str::msg_add_member(context, &added_member, from_id).await;
X_MrAddToGrp = Some(added_member);
} else if let Some(old_name) = mime_parser.get_header(HeaderDef::ChatGroupNameChanged) {
X_MrGrpNameChanged = true;
better_msg = stock_str::msg_grp_name(
context,
old_name,
@@ -1531,9 +1557,16 @@ async fn create_or_lookup_group(
// execute group commands
if X_MrAddToGrp.is_some() {
recreate_member_list = true;
} else if X_MrGrpNameChanged {
} else if mime_parser
.get_header(HeaderDef::ChatGroupNameChanged)
.is_some()
{
if let Some(ref grpname) = grpname {
if grpname.len() < 200 {
if grpname.len() < 200
&& chat_id
.update_timestamp(context, Param::GroupNameTimestamp, sent_timestamp)
.await?
{
info!(context, "updating grpname for chat {}", chat_id);
if context
.sql
@@ -1555,54 +1588,69 @@ async fn create_or_lookup_group(
if let Some(avatar_action) = &mime_parser.group_avatar {
info!(context, "group-avatar change for {}", chat_id);
if let Ok(mut chat) = Chat::load_from_db(context, chat_id).await {
match avatar_action {
AvatarAction::Change(profile_image) => {
chat.param.set(Param::ProfileImage, profile_image);
}
AvatarAction::Delete => {
chat.param.remove(Param::ProfileImage);
}
};
chat.update_param(context).await?;
send_EVENT_CHAT_MODIFIED = true;
if chat
.param
.update_timestamp(Param::AvatarTimestamp, sent_timestamp)?
{
match avatar_action {
AvatarAction::Change(profile_image) => {
chat.param.set(Param::ProfileImage, profile_image);
}
AvatarAction::Delete => {
chat.param.remove(Param::ProfileImage);
}
};
chat.update_param(context).await?;
send_EVENT_CHAT_MODIFIED = true;
}
}
}
// add members to group/check members
if recreate_member_list {
if !chat::is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF).await {
// Members could have been removed while we were
// absent. We can't use existing member list and need to
// start from scratch.
context
.sql
.execute(
"DELETE FROM chats_contacts WHERE chat_id=?;",
paramsv![chat_id],
)
.await
.ok();
chat::add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF).await;
}
if from_id > DC_CONTACT_ID_LAST_SPECIAL
&& !Contact::addr_equals_contact(context, &self_addr, from_id).await
&& !chat::is_contact_in_chat(context, chat_id, from_id).await
if chat_id
.update_timestamp(context, Param::MemberListTimestamp, sent_timestamp)
.await?
{
chat::add_to_chat_contacts_table(context, chat_id, from_id).await;
}
for &to_id in to_ids.iter() {
info!(context, "adding to={:?} to chat id={}", to_id, chat_id);
if !Contact::addr_equals_contact(context, &self_addr, to_id).await
&& !chat::is_contact_in_chat(context, chat_id, to_id).await
{
chat::add_to_chat_contacts_table(context, chat_id, to_id).await;
if !chat::is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF).await {
// Members could have been removed while we were
// absent. We can't use existing member list and need to
// start from scratch.
context
.sql
.execute(
"DELETE FROM chats_contacts WHERE chat_id=?;",
paramsv![chat_id],
)
.await
.ok();
chat::add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF).await;
}
if from_id > DC_CONTACT_ID_LAST_SPECIAL
&& !Contact::addr_equals_contact(context, &self_addr, from_id).await
&& !chat::is_contact_in_chat(context, chat_id, from_id).await
{
chat::add_to_chat_contacts_table(context, chat_id, from_id).await;
}
for &to_id in to_ids.iter() {
info!(context, "adding to={:?} to chat id={}", to_id, chat_id);
if !Contact::addr_equals_contact(context, &self_addr, to_id).await
&& !chat::is_contact_in_chat(context, chat_id, to_id).await
{
chat::add_to_chat_contacts_table(context, chat_id, to_id).await;
}
}
send_EVENT_CHAT_MODIFIED = true;
}
send_EVENT_CHAT_MODIFIED = true;
} else if let Some(contact_id) = removed_id {
chat::remove_from_chat_contacts_table(context, chat_id, contact_id).await;
send_EVENT_CHAT_MODIFIED = true;
if chat_id
.update_timestamp(context, Param::MemberListTimestamp, sent_timestamp)
.await?
{
chat::remove_from_chat_contacts_table(context, chat_id, contact_id).await;
send_EVENT_CHAT_MODIFIED = true;
}
}
if send_EVENT_CHAT_MODIFIED {