diff --git a/src/chat.rs b/src/chat.rs index 374c7eb3a..464fb2024 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -3335,7 +3335,7 @@ pub(crate) async fn shall_attach_selfavatar(context: &Context, chat_id: ChatId) } /// Chat mute duration. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum MuteDuration { /// Chat is not muted. NotMuted, @@ -3385,6 +3385,7 @@ impl rusqlite::types::FromSql for MuteDuration { /// Mutes the chat for a given duration or unmutes it. pub async fn set_muted(context: &Context, chat_id: ChatId, duration: MuteDuration) -> Result<()> { ensure!(!chat_id.is_special(), "Invalid chat ID"); + let (context, nosync) = &context.unwrap_nosync(); context .sql .execute( @@ -3394,6 +3395,11 @@ pub async fn set_muted(context: &Context, chat_id: ChatId, duration: MuteDuratio .await .context(format!("Failed to set mute duration for {chat_id}"))?; context.emit_event(EventType::ChatModified(chat_id)); + if !nosync { + let chat = Chat::load_from_db(context, chat_id).await?; + chat.add_sync_item(context, ChatAction::SetMuted(duration)) + .await?; + } Ok(()) } @@ -4048,6 +4054,7 @@ impl Context { ChatAction::Unblock => chat_id.unblock(self).await, ChatAction::Accept => chat_id.accept(self).await, ChatAction::SetVisibility(v) => chat_id.set_visibility(self, *v).await, + ChatAction::SetMuted(duration) => set_muted(self, chat_id, *duration).await, } .ok(); Ok(()) diff --git a/src/sync.rs b/src/sync.rs index c3c75459e..87fc6d4ab 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -47,6 +47,7 @@ pub(crate) enum ChatAction { Accept, SetVisibility(ChatVisibility), + SetMuted(chat::MuteDuration), } #[derive(Debug, Serialize, Deserialize)] @@ -288,6 +289,8 @@ impl Context { #[cfg(test)] mod tests { + use std::time::{Duration, SystemTime}; + use anyhow::bail; use strum::IntoEnumIterator; @@ -316,13 +319,15 @@ mod tests { assert!(t.build_sync_json().await?.is_none()); - // Having one test on `SyncData::AlterChat` is sufficient here as `AlterChatData` introduces - // enums inside items. Let's avoid in-depth testing of the serialiser here which is an - // external crate. + // Having one test on `SyncData::AlterChat` is sufficient here as `AlterChatData` with + // `ChatAction::SetMuted` introduces enums inside items and SystemTime. Let's avoid in-depth + // testing of the serialiser here which is an external crate. t.add_sync_item_with_timestamp( SyncData::AlterChat(AlterChatData { id: ChatId::ContactAddr("bob@example.net".to_string()), - action: ChatAction::Block, + action: ChatAction::SetMuted(chat::MuteDuration::Until( + SystemTime::UNIX_EPOCH + Duration::from_millis(42999), + )), }), 1631781315, ) @@ -351,7 +356,7 @@ mod tests { assert_eq!( serialized, r#"{"items":[ -{"timestamp":1631781315,"data":{"AlterChat":{"id":{"ContactAddr":"bob@example.net"},"action":"Block"}}}, +{"timestamp":1631781315,"data":{"AlterChat":{"id":{"ContactAddr":"bob@example.net"},"action":{"SetMuted":{"Until":{"secs_since_epoch":42,"nanos_since_epoch":999000000}}}}}}, {"timestamp":1631781316,"data":{"AddQrToken":{"invitenumber":"testinvite","auth":"testauth","grpid":"group123"}}}, {"timestamp":1631781317,"data":{"DeleteQrToken":{"invitenumber":"123!?\":.;{}","auth":"456","grpid":null}}} ]}"# @@ -425,16 +430,21 @@ mod tests { ) .is_err()); // Unknown enum value - // Test enums inside items + // Test enums inside items and SystemTime let sync_items = t.parse_sync_items( - r#"{"items":[{"timestamp":1631781318,"data":{"AlterChat":{"id":{"ContactAddr":"bob@example.net"},"action":"Block"}}}]}"#.to_string(), + r#"{"items":[{"timestamp":1631781318,"data":{"AlterChat":{"id":{"ContactAddr":"bob@example.net"},"action":{"SetMuted":{"Until":{"secs_since_epoch":42,"nanos_since_epoch":999000000}}}}}}]}"#.to_string(), )?; assert_eq!(sync_items.items.len(), 1); let AlterChat(AlterChatData { id, action }) = &sync_items.items.get(0).unwrap().data else { bail!("bad item"); }; assert_eq!(*id, ChatId::ContactAddr("bob@example.net".to_string())); - assert_eq!(*action, ChatAction::Block); + assert_eq!( + *action, + ChatAction::SetMuted(chat::MuteDuration::Until( + SystemTime::UNIX_EPOCH + Duration::from_millis(42999) + )) + ); // empty item list is okay assert_eq!( @@ -607,6 +617,31 @@ mod tests { assert_eq!(alices[1].get_chat(&bob).await.get_visibility(), v); } + use chat::MuteDuration; + assert_eq!( + alices[1].get_chat(&bob).await.mute_duration, + MuteDuration::NotMuted + ); + let mute_durations = [ + MuteDuration::Forever, + MuteDuration::Until(SystemTime::now() + Duration::from_secs(42)), + MuteDuration::NotMuted, + ]; + for m in mute_durations { + chat::set_muted(&alices[0], a0b_chat_id, m).await?; + sync(&alices).await?; + let m = match m { + MuteDuration::Until(time) => MuteDuration::Until( + SystemTime::UNIX_EPOCH + + Duration::from_secs( + time.duration_since(SystemTime::UNIX_EPOCH)?.as_secs(), + ), + ), + _ => m, + }; + assert_eq!(alices[1].get_chat(&bob).await.mute_duration, m); + } + Ok(()) } }