diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 50bd8318a..cac7c6e1a 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -5368,6 +5368,14 @@ void dc_event_unref(dc_event_t* event); */ #define DC_EVENT_CONNECTIVITY_CHANGED 2100 + +/** + * The user's avatar changed. + * You can get the new avatar file with `dc_get_config(context, "selfavatar")`. + */ +#define DC_EVENT_SELFAVATAR_CHANGED 2110 + + /** * @} */ diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index eaba90f34..f1542786b 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -438,6 +438,7 @@ pub unsafe extern "C" fn dc_event_get_data1_int(event: *mut dc_event_t) -> libc: | EventType::Warning(_) | EventType::Error(_) | EventType::ConnectivityChanged + | EventType::SelfavatarChanged | EventType::ErrorSelfNotInGroup(_) => 0, EventType::MsgsChanged { chat_id, .. } | EventType::IncomingMsg { chat_id, .. } @@ -488,6 +489,7 @@ pub unsafe extern "C" fn dc_event_get_data2_int(event: *mut dc_event_t) -> libc: | EventType::ImexFileWritten(_) | EventType::MsgsNoticed(_) | EventType::ConnectivityChanged + | EventType::SelfavatarChanged | EventType::ChatModified(_) => 0, EventType::MsgsChanged { msg_id, .. } | EventType::IncomingMsg { msg_id, .. } @@ -537,6 +539,7 @@ pub unsafe extern "C" fn dc_event_get_data2_str(event: *mut dc_event_t) -> *mut | EventType::SecurejoinInviterProgress { .. } | EventType::SecurejoinJoinerProgress { .. } | EventType::ConnectivityChanged + | EventType::SelfavatarChanged | EventType::ChatEphemeralTimerModified { .. } => ptr::null_mut(), EventType::ConfigureProgress { comment, .. } => { if let Some(comment) = comment { diff --git a/src/blob.rs b/src/blob.rs index cd32771b0..8f4e4aa24 100644 --- a/src/blob.rs +++ b/src/blob.rs @@ -621,7 +621,10 @@ mod tests { use super::*; - use crate::{message::Message, test_utils::TestContext}; + use crate::{ + message::Message, + test_utils::{self, TestContext}, + }; use image::Pixel; #[async_std::test] @@ -870,11 +873,10 @@ mod tests { async fn test_selfavatar_in_blobdir() { let t = TestContext::new().await; let avatar_src = t.get_blobdir().join("avatar.png"); - let avatar_bytes = include_bytes!("../test-data/image/avatar900x900.png"); File::create(&avatar_src) .await .unwrap() - .write_all(avatar_bytes) + .write_all(test_utils::AVATAR_900x900_BYTES) .await .unwrap(); diff --git a/src/config.rs b/src/config.rs index d4c71afcb..f7ae28cfb 100644 --- a/src/config.rs +++ b/src/config.rs @@ -279,13 +279,13 @@ impl Context { let mut blob = BlobObject::new_from_path(self, value.as_ref()).await?; blob.recode_to_avatar_size(self).await?; self.sql.set_raw_config(key, Some(blob.as_name())).await?; - Ok(()) } None => { self.sql.set_raw_config(key, None).await?; - Ok(()) } } + self.emit_event(EventType::SelfavatarChanged); + Ok(()) } Config::Selfstatus => { let def = stock_str::status_line(self).await; diff --git a/src/contact.rs b/src/contact.rs index 70c93c79c..2e86001a9 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -1371,11 +1371,14 @@ fn split_address_book(book: &str) -> Vec<(&str, &str)> { #[cfg(test)] mod tests { + use async_std::fs::File; + use async_std::io::WriteExt; + use super::*; use crate::chat::send_text_msg; use crate::message::Message; - use crate::test_utils::TestContext; + use crate::test_utils::{self, TestContext}; #[test] fn test_may_be_valid_addr() { @@ -1971,4 +1974,64 @@ CCCB 5AA9 F6E1 141C 9431 Ok(()) } + + /// Tests that DC_EVENT_SELFAVATAR_CHANGED is emitted on avatar changes. + #[async_std::test] + async fn test_selfavatar_changed_event() -> Result<()> { + // Alice has two devices. + let alice1 = TestContext::new_alice().await; + let alice2 = TestContext::new_alice().await; + + // Bob has one device. + let bob = TestContext::new_bob().await; + + assert_eq!(alice1.get_config(Config::Selfavatar).await?, None); + + let avatar_src = alice1.get_blobdir().join("avatar.png"); + File::create(&avatar_src) + .await? + .write_all(test_utils::AVATAR_900x900_BYTES) + .await?; + + alice1 + .set_config(Config::Selfavatar, Some(avatar_src.to_str().unwrap())) + .await?; + + alice1 + .evtracker + .get_matching(|e| e == EventType::SelfavatarChanged) + .await; + + // Bob sends a message so that Alice can encrypt to him. + let chat = bob + .create_chat_with_contact("Alice", "alice@example.com") + .await; + + send_text_msg(&bob, chat.id, "Reply".to_string()).await?; + let sent_msg = bob.pop_sent_msg().await; + alice1.recv_msg(&sent_msg).await; + alice2.recv_msg(&sent_msg).await; + + // Alice sends a message. + let alice1_chat_id = alice1.get_last_msg().await.chat_id; + alice1_chat_id.accept(&alice1).await?; + send_text_msg(&alice1, alice1_chat_id, "Hello".to_string()).await?; + let sent_msg = alice1.pop_sent_msg().await; + + // The message is encrypted. + let message = Message::load_from_db(&alice1, sent_msg.sender_msg_id).await?; + assert!(message.get_showpadlock()); + + // Alice's second device receives a copy of the outgoing message. + alice2.recv_msg(&sent_msg).await; + + // Alice's second device applies the selfavatar. + assert!(alice2.get_config(Config::Selfavatar).await?.is_some()); + alice2 + .evtracker + .get_matching(|e| e == EventType::SelfavatarChanged) + .await; + + Ok(()) + } } diff --git a/src/dc_tools.rs b/src/dc_tools.rs index cd7b1ebd2..217967bf9 100644 --- a/src/dc_tools.rs +++ b/src/dc_tools.rs @@ -835,8 +835,8 @@ mod tests { assert_eq!("@d.tt".parse::().is_ok(), false); } - use crate::chat; use crate::chatlist::Chatlist; + use crate::{chat, test_utils}; use chrono::{NaiveDate, NaiveDateTime, NaiveTime}; use proptest::prelude::*; @@ -986,8 +986,7 @@ mod tests { #[test] fn test_get_filemeta() { - let data = include_bytes!("../test-data/image/avatar900x900.png"); - let (w, h) = dc_get_filemeta(data).unwrap(); + let (w, h) = dc_get_filemeta(test_utils::AVATAR_900x900_BYTES).unwrap(); assert_eq!(w, 900); assert_eq!(h, 900); diff --git a/src/events.rs b/src/events.rs index eb460d769..c700b065a 100644 --- a/src/events.rs +++ b/src/events.rs @@ -323,4 +323,7 @@ pub enum EventType { /// dc_get_connectivity_html() for details. #[strum(props(id = "2100"))] ConnectivityChanged, + + #[strum(props(id = "2110"))] + SelfavatarChanged, } diff --git a/src/test_utils.rs b/src/test_utils.rs index 72b1b4700..ac91be18b 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -34,6 +34,9 @@ use crate::message::{update_msg_state, Message, MessageState, MsgId}; use crate::mimeparser::MimeMessage; use crate::param::{Param, Params}; +#[allow(non_upper_case_globals)] +pub const AVATAR_900x900_BYTES: &[u8] = include_bytes!("../test-data/image/avatar900x900.png"); + type EventSink = dyn Fn(Event) -> Pin + Send + 'static>> + Send + Sync + 'static; @@ -587,6 +590,21 @@ impl EvTracker { } } } + + pub async fn get_matching bool>(&self, event_matcher: F) -> EventType { + const TIMEOUT: Duration = Duration::from_secs(20); + + loop { + let event = async_std::future::timeout(TIMEOUT, self.recv()) + .await + .unwrap() + .unwrap(); + + if event_matcher(event.clone()) { + return event; + } + } + } } impl Deref for EvTracker {