mirror of
https://github.com/chatmail/core.git
synced 2026-05-20 07:16:31 +03:00
feat: Sync chat mute_duration across devices (#4817)
This commit is contained in:
@@ -3335,7 +3335,7 @@ pub(crate) async fn shall_attach_selfavatar(context: &Context, chat_id: ChatId)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Chat mute duration.
|
/// Chat mute duration.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum MuteDuration {
|
pub enum MuteDuration {
|
||||||
/// Chat is not muted.
|
/// Chat is not muted.
|
||||||
NotMuted,
|
NotMuted,
|
||||||
@@ -3385,6 +3385,7 @@ impl rusqlite::types::FromSql for MuteDuration {
|
|||||||
/// Mutes the chat for a given duration or unmutes it.
|
/// Mutes the chat for a given duration or unmutes it.
|
||||||
pub async fn set_muted(context: &Context, chat_id: ChatId, duration: MuteDuration) -> Result<()> {
|
pub async fn set_muted(context: &Context, chat_id: ChatId, duration: MuteDuration) -> Result<()> {
|
||||||
ensure!(!chat_id.is_special(), "Invalid chat ID");
|
ensure!(!chat_id.is_special(), "Invalid chat ID");
|
||||||
|
let (context, nosync) = &context.unwrap_nosync();
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
@@ -3394,6 +3395,11 @@ pub async fn set_muted(context: &Context, chat_id: ChatId, duration: MuteDuratio
|
|||||||
.await
|
.await
|
||||||
.context(format!("Failed to set mute duration for {chat_id}"))?;
|
.context(format!("Failed to set mute duration for {chat_id}"))?;
|
||||||
context.emit_event(EventType::ChatModified(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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4048,6 +4054,7 @@ impl Context {
|
|||||||
ChatAction::Unblock => chat_id.unblock(self).await,
|
ChatAction::Unblock => chat_id.unblock(self).await,
|
||||||
ChatAction::Accept => chat_id.accept(self).await,
|
ChatAction::Accept => chat_id.accept(self).await,
|
||||||
ChatAction::SetVisibility(v) => chat_id.set_visibility(self, *v).await,
|
ChatAction::SetVisibility(v) => chat_id.set_visibility(self, *v).await,
|
||||||
|
ChatAction::SetMuted(duration) => set_muted(self, chat_id, *duration).await,
|
||||||
}
|
}
|
||||||
.ok();
|
.ok();
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
51
src/sync.rs
51
src/sync.rs
@@ -47,6 +47,7 @@ pub(crate) enum ChatAction {
|
|||||||
|
|
||||||
Accept,
|
Accept,
|
||||||
SetVisibility(ChatVisibility),
|
SetVisibility(ChatVisibility),
|
||||||
|
SetMuted(chat::MuteDuration),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
@@ -288,6 +289,8 @@ impl Context {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use std::time::{Duration, SystemTime};
|
||||||
|
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
@@ -316,13 +319,15 @@ mod tests {
|
|||||||
|
|
||||||
assert!(t.build_sync_json().await?.is_none());
|
assert!(t.build_sync_json().await?.is_none());
|
||||||
|
|
||||||
// Having one test on `SyncData::AlterChat` is sufficient here as `AlterChatData` introduces
|
// Having one test on `SyncData::AlterChat` is sufficient here as `AlterChatData` with
|
||||||
// enums inside items. Let's avoid in-depth testing of the serialiser here which is an
|
// `ChatAction::SetMuted` introduces enums inside items and SystemTime. Let's avoid in-depth
|
||||||
// external crate.
|
// testing of the serialiser here which is an external crate.
|
||||||
t.add_sync_item_with_timestamp(
|
t.add_sync_item_with_timestamp(
|
||||||
SyncData::AlterChat(AlterChatData {
|
SyncData::AlterChat(AlterChatData {
|
||||||
id: ChatId::ContactAddr("bob@example.net".to_string()),
|
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,
|
1631781315,
|
||||||
)
|
)
|
||||||
@@ -351,7 +356,7 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
serialized,
|
serialized,
|
||||||
r#"{"items":[
|
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":1631781316,"data":{"AddQrToken":{"invitenumber":"testinvite","auth":"testauth","grpid":"group123"}}},
|
||||||
{"timestamp":1631781317,"data":{"DeleteQrToken":{"invitenumber":"123!?\":.;{}","auth":"456","grpid":null}}}
|
{"timestamp":1631781317,"data":{"DeleteQrToken":{"invitenumber":"123!?\":.;{}","auth":"456","grpid":null}}}
|
||||||
]}"#
|
]}"#
|
||||||
@@ -425,16 +430,21 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.is_err()); // Unknown enum value
|
.is_err()); // Unknown enum value
|
||||||
|
|
||||||
// Test enums inside items
|
// Test enums inside items and SystemTime
|
||||||
let sync_items = t.parse_sync_items(
|
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);
|
assert_eq!(sync_items.items.len(), 1);
|
||||||
let AlterChat(AlterChatData { id, action }) = &sync_items.items.get(0).unwrap().data else {
|
let AlterChat(AlterChatData { id, action }) = &sync_items.items.get(0).unwrap().data else {
|
||||||
bail!("bad item");
|
bail!("bad item");
|
||||||
};
|
};
|
||||||
assert_eq!(*id, ChatId::ContactAddr("bob@example.net".to_string()));
|
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
|
// empty item list is okay
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -607,6 +617,31 @@ mod tests {
|
|||||||
assert_eq!(alices[1].get_chat(&bob).await.get_visibility(), v);
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user