Merge remote-tracking branch 'origin/master' into feat/async-jobs

This commit is contained in:
dignifiedquire
2020-05-13 18:29:22 +02:00
83 changed files with 4602 additions and 2766 deletions

View File

@@ -15,7 +15,7 @@ use crate::constants::*;
use crate::contact::*;
use crate::context::Context;
use crate::dc_tools::*;
use crate::error::Error;
use crate::error::{bail, ensure, format_err, Error};
use crate::events::Event;
use crate::job::{self, Action};
use crate::message::{self, InvalidMsgId, Message, MessageState, MsgId};
@@ -340,13 +340,12 @@ impl ChatId {
time(),
msg.viewtype,
MessageState::OutDraft,
msg.text.as_ref().cloned().unwrap_or_default(),
msg.text.as_deref().unwrap_or(""),
msg.param.to_string(),
1,
],
)
.await?;
Ok(())
}
@@ -399,6 +398,62 @@ impl ChatId {
Ok(self.get_param(context).await?.exists(Param::Devicetalk))
}
async fn parent_query<T, F>(
self,
context: &Context,
fields: &str,
f: F,
) -> sql::Result<Option<T>>
where
F: FnOnce(&rusqlite::Row) -> rusqlite::Result<T>,
{
let sql = &context.sql;
let query = format!(
"SELECT {} \
FROM msgs WHERE chat_id=? AND state NOT IN (?, ?, ?, ?) AND NOT hidden \
ORDER BY timestamp DESC, id DESC \
LIMIT 1;",
fields
);
sql.query_row_optional(
query,
paramsv![
self,
MessageState::OutPreparing,
MessageState::OutDraft,
MessageState::OutPending,
MessageState::OutFailed
],
f,
)
.await
}
async fn get_parent_mime_headers(self, context: &Context) -> Option<(String, String, String)> {
let collect = |row: &rusqlite::Row| Ok((row.get(0)?, row.get(1)?, row.get(2)?));
self.parent_query(
context,
"rfc724_mid, mime_in_reply_to, mime_references",
collect,
)
.await
.ok()
.flatten()
}
async fn parent_is_encrypted(self, context: &Context) -> Result<bool, Error> {
let collect = |row: &rusqlite::Row| Ok(row.get(0)?);
let packed: Option<String> = self.parent_query(context, "param", collect).await?;
if let Some(ref packed) = packed {
let param = packed.parse::<Params>()?;
Ok(param.exists(Param::GuaranteeE2ee))
} else {
// No messages
Ok(false)
}
}
/// Bad evil escape hatch.
///
/// Avoid using this, eventually types should be cleaned up enough
@@ -580,44 +635,6 @@ impl Chat {
&self.name
}
pub async fn get_subtitle(&self, context: &Context) -> String {
// returns either the address or the number of chat members
if self.typ == Chattype::Single && self.param.exists(Param::Selftalk) {
return context
.stock_str(StockMessage::SelfTalkSubTitle)
.await
.into();
}
if self.typ == Chattype::Single {
return context
.sql
.query_get_value(
context,
"SELECT c.addr
FROM chats_contacts cc
LEFT JOIN contacts c ON c.id=cc.contact_id
WHERE cc.chat_id=?;",
paramsv![self.id],
)
.await
.unwrap_or_else(|| "Err".into());
}
if self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup {
if self.id.is_deaddrop() {
return context.stock_str(StockMessage::DeadDrop).await.into();
}
let cnt = get_chat_contact_cnt(context, self.id).await;
return context
.stock_string_repl_int(StockMessage::Member, cnt as i32)
.await;
}
"Err".to_string()
}
fn parent_query(fields: &str) -> String {
// Check for server_uid guarantees that we don't
// select a draft or undelivered message.
@@ -717,7 +734,6 @@ impl Chat {
.await
.map(Into::into)
.unwrap_or_else(std::path::PathBuf::new),
subtitle: self.get_subtitle(context).await,
draft,
is_muted: self.is_muted(),
})
@@ -873,7 +889,7 @@ impl Chat {
}
}
if can_encrypt && (all_mutual || self.parent_is_encrypted(context).await?) {
if can_encrypt && (all_mutual || self.id.parent_is_encrypted(context).await?) {
msg.param.set_int(Param::GuaranteeE2ee, 1);
}
}
@@ -888,7 +904,7 @@ impl Chat {
// we do not set In-Reply-To/References in this case.
if !self.is_self_talk() {
if let Some((parent_rfc724_mid, parent_in_reply_to, parent_references)) =
self.get_parent_mime_headers(context).await
self.id.get_parent_mime_headers(context).await
{
if !parent_rfc724_mid.is_empty() {
new_in_reply_to = parent_rfc724_mid.clone();
@@ -1067,9 +1083,6 @@ pub struct ChatInfo {
/// currently.
pub profile_image: std::path::PathBuf,
/// Subtitle for the chat.
pub subtitle: String,
/// The draft message text.
///
/// If the chat has not draft this is an empty string.
@@ -1535,7 +1548,7 @@ pub async fn get_chat_msgs(
flags: u32,
marker1before: Option<MsgId>,
) -> Vec<MsgId> {
match hide_device_expired_messages(context).await {
match delete_device_expired_messages(context).await {
Err(err) => warn!(context, "Failed to delete expired messages: {}", err),
Ok(messages_deleted) => {
if messages_deleted {
@@ -1700,11 +1713,11 @@ pub async fn marknoticed_all_chats(context: &Context) -> Result<(), Error> {
Ok(())
}
/// Hides messages which are expired according to "delete_device_after" setting.
/// Deletes messages which are expired according to "delete_device_after" setting.
///
/// Returns true if any message is hidden, so event can be emitted. If nothing
/// has been hidden, returns false.
pub async fn hide_device_expired_messages(context: &Context) -> Result<bool, Error> {
/// Returns true if any message is deleted, so event can be emitted. If nothing
/// has been deleted, returns false.
pub async fn delete_device_expired_messages(context: &Context) -> Result<bool, Error> {
if let Some(delete_device_after) = context.get_config_delete_device_after().await {
let threshold_timestamp = time() - delete_device_after;
@@ -1717,7 +1730,7 @@ pub async fn hide_device_expired_messages(context: &Context) -> Result<bool, Err
.unwrap_or_default()
.0;
// Hide expired messages
// Delete expired messages
//
// Only update the rows that have to be updated, to avoid emitting
// unnecessary "chat modified" events.
@@ -1725,13 +1738,14 @@ pub async fn hide_device_expired_messages(context: &Context) -> Result<bool, Err
.sql
.execute(
"UPDATE msgs \
SET txt = 'DELETED', hidden = 1 \
SET txt = 'DELETED', chat_id = ? \
WHERE timestamp < ? \
AND chat_id > ? \
AND chat_id != ? \
AND chat_id != ? \
AND NOT hidden",
paramsv![
DC_CHAT_ID_TRASH,
threshold_timestamp,
DC_CHAT_ID_LAST_SPECIAL,
self_chat_id,
@@ -1822,17 +1836,17 @@ pub async fn get_next_media(
msg_type3,
)
.await;
for i in 0..list.len() {
if curr_msg_id == list[i] {
for (i, msg_id) in list.iter().enumerate() {
if curr_msg_id == *msg_id {
match direction {
Direction::Forward => {
if i + 1 < list.len() {
ret = Some(list[i + 1]);
ret = list.get(i + 1).copied();
}
}
Direction::Backward => {
if i >= 1 {
ret = Some(list[i - 1]);
ret = list.get(i - 1).copied();
}
}
}
@@ -1918,38 +1932,56 @@ pub async fn create_group_chat(
Ok(chat_id)
}
/// add a contact to the chats_contact table
pub(crate) async fn add_to_chat_contacts_table(
context: &Context,
chat_id: ChatId,
contact_id: u32,
) -> bool {
// add a contact to a chat; the function does not check the type or if any of the record exist or are already
// added to the chat!
context
match context
.sql
.execute(
"INSERT INTO chats_contacts (chat_id, contact_id) VALUES(?, ?)",
paramsv![chat_id, contact_id as i32],
)
.await
.is_ok()
{
Ok(_) => true,
Err(err) => {
error!(
context,
"could not add {} to chat {} table: {}", contact_id, chat_id, err
);
false
}
}
}
/// remove a contact from the chats_contact table
pub(crate) async fn remove_from_chat_contacts_table(
context: &Context,
chat_id: ChatId,
contact_id: u32,
) -> bool {
// remove a contact from the chats_contact table unconditionally
// the function does not check the type or if the record exist
context
match context
.sql
.execute(
"DELETE FROM chats_contacts WHERE chat_id=? AND contact_id=?",
paramsv![chat_id, contact_id as i32],
)
.await
.is_ok()
{
Ok(_) => true,
Err(_) => {
warn!(
context,
"could not remove contact {:?} from chat {:?}", contact_id, chat_id
);
false
}
}
}
/// Adds a contact to the chat.
@@ -2049,15 +2081,7 @@ pub(crate) async fn add_contact_to_chat_ex(
msg.param.set(Param::Arg, contact.get_addr());
msg.param.set_int(Param::Arg2, from_handshake.into());
msg.id = send_msg(context, chat_id, &mut msg).await?;
context.call_cb(Event::MsgsChanged {
chat_id,
msg_id: msg.id,
});
}
context.call_cb(Event::MsgsChanged {
chat_id,
msg_id: MsgId::new(0),
});
context.call_cb(Event::ChatModified(chat_id));
Ok(true)
}
@@ -2211,19 +2235,18 @@ pub async fn set_muted(
duration: MuteDuration,
) -> Result<(), Error> {
ensure!(!chat_id.is_special(), "Invalid chat ID");
if real_group_exists(context, chat_id).await
&& context
.sql
.execute(
"UPDATE chats SET muted_until=? WHERE id=?;",
paramsv![duration, chat_id],
)
.await
.is_ok()
if context
.sql
.execute(
"UPDATE chats SET muted_until=? WHERE id=?;",
paramsv![duration, chat_id],
)
.await
.is_ok()
{
context.call_cb(Event::ChatModified(chat_id));
} else {
bail!("Failed to set name");
bail!("Failed to set mute duration, chat might not exist -");
}
Ok(())
}
@@ -2257,8 +2280,7 @@ pub async fn remove_contact_from_chat(
"Cannot remove contact from chat; self not in group.".into()
)
);
} else if remove_from_chat_contacts_table(context, chat_id, contact_id).await {
/* we should respect this - whatever we send to the group, it gets discarded anyway! */
} else {
if let Ok(contact) = Contact::get_by_id(context, contact_id).await {
if chat.is_promoted() {
msg.viewtype = Viewtype::Text;
@@ -2289,14 +2311,20 @@ pub async fn remove_contact_from_chat(
msg.param.set_cmd(SystemMessage::MemberRemovedFromGroup);
msg.param.set(Param::Arg, contact.get_addr());
msg.id = send_msg(context, chat_id, &mut msg).await?;
context.call_cb(Event::MsgsChanged {
chat_id,
msg_id: msg.id,
});
}
}
// we remove the member from the chat after constructing the
// to-be-send message. If between send_msg() and here the
// process dies the user will have to re-do the action. It's
// better than the other way round: you removed
// someone from DB but no peer or device gets to know about it and
// group membership is thus different on different devices.
// Note also that sending a message needs all recipients
// in order to correctly determine encryption so if we
// removed it first, it would complicate the
// check/encryption logic.
success = remove_from_chat_contacts_table(context, chat_id, contact_id).await;
context.call_cb(Event::ChatModified(chat_id));
success = true;
}
}
}
@@ -2781,7 +2809,6 @@ mod tests {
"is_sending_locations": false,
"color": 15895624,
"profile_image": "",
"subtitle": "bob@example.com",
"draft": "",
"is_muted": false
}
@@ -3402,4 +3429,18 @@ mod tests {
false
);
}
#[async_std::test]
async fn test_parent_is_encrypted() {
let t = dummy_context().await;
let chat_id = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo")
.await
.unwrap();
assert!(!chat_id.parent_is_encrypted(&t.ctx).await.unwrap());
let mut msg = Message::new(Viewtype::Text);
msg.set_text(Some("hello".to_string()));
chat_id.set_draft(&t.ctx, Some(&mut msg)).await;
assert!(!chat_id.parent_is_encrypted(&t.ctx).await.unwrap());
}
}