feat(ffi): Add methods for observing account data changes

Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
This commit is contained in:
Johannes Marbach
2025-04-30 22:57:46 +02:00
committed by Damir Jelić
parent afabfb97b6
commit 175d854a9b
3 changed files with 849 additions and 7 deletions

View File

@@ -6,6 +6,7 @@ use std::{
use anyhow::{anyhow, Context as _};
use async_compat::get_runtime_handle;
use futures_util::StreamExt;
use matrix_sdk::{
authentication::oauth::{
AccountManagementActionFull, ClientId, OAuthAuthorizationData, OAuthSession,
@@ -45,8 +46,13 @@ use mime::Mime;
use ruma::{
api::client::{alias::get_alias, error::ErrorKind, uiaa::UserIdentifier},
events::{
direct::DirectEventContent,
fully_read::FullyReadEventContent,
identity_server::IdentityServerEventContent,
ignored_user_list::IgnoredUserListEventContent,
key::verification::request::ToDeviceKeyVerificationRequestEvent,
marked_unread::{MarkedUnreadEventContent, UnstableMarkedUnreadEventContent},
push_rules::PushRulesEventContent,
room::{
history_visibility::RoomHistoryVisibilityEventContent,
join_rules::{
@@ -55,7 +61,13 @@ use ruma::{
message::OriginalSyncRoomMessageEvent,
power_levels::RoomPowerLevelsEventContent,
},
GlobalAccountDataEventType,
secret_storage::{
default_key::SecretStorageDefaultKeyEventContent, key::SecretStorageKeyEventContent,
},
tag::TagEventContent,
GlobalAccountDataEvent as RumaGlobalAccountDataEvent,
GlobalAccountDataEventType as RumaGlobalAccountDataEventType,
RoomAccountDataEvent as RumaRoomAccountDataEvent,
},
push::{HttpPusherData as RumaHttpPusherData, PushFormat as RumaPushFormat},
OwnedServerName, RoomAliasId, RoomOrAliasId, ServerName,
@@ -76,7 +88,10 @@ use crate::{
room::RoomHistoryVisibility,
room_directory_search::RoomDirectorySearch,
room_preview::RoomPreview,
ruma::{AuthData, MediaSource},
ruma::{
AccountDataEvent, AccountDataEventType, AuthData, MediaSource, RoomAccountDataEvent,
RoomAccountDataEventType,
},
sync_service::{SyncService, SyncServiceBuilder},
task_handle::TaskHandle,
utils::AsyncRuntimeDropped,
@@ -168,6 +183,20 @@ pub trait SendQueueRoomErrorListener: Sync + Send {
fn on_error(&self, room_id: String, error: ClientError);
}
/// A listener for changes of global account data events.
#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait AccountDataListener: Sync + Send {
/// Called when a global account data event has changed.
fn on_change(&self, event: AccountDataEvent);
}
/// A listener for changes of room account data events.
#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait RoomAccountDataListener: Sync + Send {
/// Called when a room account data event was changed.
fn on_change(&self, event: RoomAccountDataEvent, room_id: String);
}
#[derive(Clone, Copy, uniffi::Record)]
pub struct TransmissionProgress {
pub current: u64,
@@ -545,6 +574,198 @@ impl Client {
})))
}
/// Subscribe to updates of global account data events.
///
/// Be careful that only the most recent value can be observed. Subscribers
/// are notified when a new value is sent, but there is no guarantee that
/// they will see all values.
pub fn observe_account_data_event(
&self,
event_type: AccountDataEventType,
listener: Box<dyn AccountDataListener>,
) -> Arc<TaskHandle> {
match event_type {
AccountDataEventType::Direct => {
// Using an Arc here is mandatory or else the subscriber will never trigger
let observer = Arc::new(
self.inner
.observe_events::<RumaGlobalAccountDataEvent<DirectEventContent>, ()>(),
);
Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
let mut subscriber = observer.subscribe();
loop {
if let Some(next) = subscriber.next().await {
listener.on_change(next.0.into());
}
}
})))
}
AccountDataEventType::IdentityServer => {
// Using an Arc here is mandatory or else the subscriber will never trigger
let observer = Arc::new(self.inner.observe_events::<RumaGlobalAccountDataEvent<IdentityServerEventContent>, ()>());
Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
let mut subscriber = observer.subscribe();
loop {
if let Some(next) = subscriber.next().await {
listener.on_change(next.0.into());
}
}
})))
}
AccountDataEventType::IgnoredUserList => {
// Using an Arc here is mandatory or else the subscriber will never trigger
let observer = Arc::new(self.inner.observe_events::<RumaGlobalAccountDataEvent<IgnoredUserListEventContent>, ()>());
Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
let mut subscriber = observer.subscribe();
loop {
if let Some(next) = subscriber.next().await {
listener.on_change(next.0.into());
}
}
})))
}
AccountDataEventType::PushRules => {
// Using an Arc here is mandatory or else the subscriber will never trigger
let observer = Arc::new(
self.inner
.observe_events::<RumaGlobalAccountDataEvent<PushRulesEventContent>, ()>(),
);
Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
let mut subscriber = observer.subscribe();
loop {
if let Some(next) = subscriber.next().await {
if let Ok(event) = next.0.try_into() {
listener.on_change(event);
}
}
}
})))
}
AccountDataEventType::SecretStorageDefaultKey => {
// Using an Arc here is mandatory or else the subscriber will never trigger
let observer = Arc::new(self.inner.observe_events::<RumaGlobalAccountDataEvent<SecretStorageDefaultKeyEventContent>, ()>());
Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
let mut subscriber = observer.subscribe();
loop {
if let Some(next) = subscriber.next().await {
listener.on_change(next.0.into());
}
}
})))
}
AccountDataEventType::SecretStorageKey(key_id) => {
// Using an Arc here is mandatory or else the subscriber will never trigger
let observer = Arc::new(self.inner.observe_events::<RumaGlobalAccountDataEvent<SecretStorageKeyEventContent>, ()>());
Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
let mut subscriber = observer.subscribe();
loop {
if let Some(next) = subscriber.next().await {
if next.0.content.key_id != key_id {
continue;
}
if let Ok(event) = next.0.try_into() {
listener.on_change(event);
}
}
}
})))
}
}
}
/// Subscribe to updates of room account data events.
///
/// Be careful that only the most recent value can be observed. Subscribers
/// are notified when a new value is sent, but there is no guarantee that
/// they will see all values.
pub fn observe_room_account_data_event(
&self,
room_id: String,
event_type: RoomAccountDataEventType,
listener: Box<dyn RoomAccountDataListener>,
) -> Result<Arc<TaskHandle>, ClientError> {
match event_type {
RoomAccountDataEventType::FullyRead => {
// Using an Arc here is mandatory or else the subscriber will never trigger
let observer = Arc::new(
self.inner
.observe_room_events::<RumaRoomAccountDataEvent<FullyReadEventContent>, ()>(
&RoomId::parse(&room_id)?,
),
);
Ok(Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
let mut subscriber = observer.subscribe();
loop {
if let Some(next) = subscriber.next().await {
listener.on_change(next.0.into(), room_id.clone());
}
}
}))))
}
RoomAccountDataEventType::MarkedUnread => {
// Using an Arc here is mandatory or else the subscriber will never trigger
let observer = Arc::new(self.inner.observe_room_events::<RumaRoomAccountDataEvent<
MarkedUnreadEventContent,
>, ()>(&RoomId::parse(
&room_id,
)?));
Ok(Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
let mut subscriber = observer.subscribe();
loop {
if let Some(next) = subscriber.next().await {
listener.on_change(next.0.into(), room_id.clone());
}
}
}))))
}
RoomAccountDataEventType::Tag => {
// Using an Arc here is mandatory or else the subscriber will never trigger
let observer = Arc::new(
self.inner
.observe_room_events::<RumaRoomAccountDataEvent<TagEventContent>, ()>(
&RoomId::parse(&room_id)?,
),
);
Ok(Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
let mut subscriber = observer.subscribe();
loop {
if let Some(next) = subscriber.next().await {
if let Ok(event) = next.0.try_into() {
listener.on_change(event, room_id.clone());
}
}
}
}))))
}
RoomAccountDataEventType::UnstableMarkedUnread => {
// Using an Arc here is mandatory or else the subscriber will never trigger
let observer = Arc::new(self.inner.observe_room_events::<RumaRoomAccountDataEvent<
UnstableMarkedUnreadEventContent,
>, ()>(&RoomId::parse(
&room_id,
)?));
Ok(Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
let mut subscriber = observer.subscribe();
loop {
if let Some(next) = subscriber.next().await {
listener.on_change(next.0.into(), room_id.clone());
}
}
}))))
}
}
}
/// Allows generic GET requests to be made through the SDKs internal HTTP
/// client
pub async fn get_url(&self, url: String) -> Result<String, ClientError> {
@@ -953,7 +1174,7 @@ impl Client {
if let Some(raw_content) = self
.inner
.account()
.fetch_account_data(GlobalAccountDataEventType::IgnoredUserList)
.fetch_account_data(RumaGlobalAccountDataEventType::IgnoredUserList)
.await?
{
let content = raw_content.deserialize_as::<IgnoredUserListEventContent>()?;

View File

@@ -161,7 +161,7 @@ pub enum PushCondition {
}
impl TryFrom<SdkPushCondition> for PushCondition {
type Error = ();
type Error = String;
fn try_from(value: SdkPushCondition) -> Result<Self, Self::Error> {
Ok(match value {
@@ -179,7 +179,7 @@ impl TryFrom<SdkPushCondition> for PushCondition {
SdkPushCondition::EventPropertyContains { key, value } => {
Self::EventPropertyContains { key, value: value.into() }
}
_ => return Err(()),
_ => return Err("Unsupported condition type".to_owned()),
})
}
}

View File

@@ -12,7 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::{collections::BTreeSet, sync::Arc, time::Duration};
use std::{
collections::{BTreeSet, HashMap},
sync::Arc,
time::Duration,
};
use extension_trait::extension_trait;
use matrix_sdk::attachment::{BaseAudioInfo, BaseFileInfo, BaseImageInfo, BaseVideoInfo};
@@ -20,8 +24,14 @@ use ruma::{
assign,
events::{
call::notify::NotifyType as RumaNotifyType,
direct::DirectEventContent,
fully_read::FullyReadEventContent,
identity_server::IdentityServerEventContent,
ignored_user_list::{IgnoredUser as RumaIgnoredUser, IgnoredUserListEventContent},
location::AssetType as RumaAssetType,
marked_unread::{MarkedUnreadEventContent, UnstableMarkedUnreadEventContent},
poll::start::PollKind as RumaPollKind,
push_rules::PushRulesEventContent,
room::{
message::{
AudioInfo as RumaAudioInfo,
@@ -43,16 +53,39 @@ use ruma::{
ImageInfo as RumaImageInfo, MediaSource as RumaMediaSource,
ThumbnailInfo as RumaThumbnailInfo,
},
secret_storage::{
default_key::SecretStorageDefaultKeyEventContent,
key::{
PassPhrase as RumaPassPhrase,
SecretStorageEncryptionAlgorithm as RumaSecretStorageEncryptionAlgorithm,
SecretStorageKeyEventContent,
SecretStorageV1AesHmacSha2Properties as RumaSecretStorageV1AesHmacSha2Properties,
},
},
tag::{
TagEventContent, TagInfo as RumaTagInfo, TagName as RumaTagName,
UserTagName as RumaUserTagName,
},
GlobalAccountDataEvent as RumaGlobalAccountDataEvent,
GlobalAccountDataEventType as RumaGlobalAccountDataEventType,
RoomAccountDataEvent as RumaRoomAccountDataEvent,
RoomAccountDataEventType as RumaRoomAccountDataEventType,
},
matrix_uri::MatrixId as RumaMatrixId,
push::{
ConditionalPushRule as RumaConditionalPushRule, PatternedPushRule as RumaPatternedPushRule,
Ruleset as RumaRuleset, SimplePushRule as RumaSimplePushRule,
},
serde::JsonObject,
MatrixToUri, MatrixUri as RumaMatrixUri, OwnedUserId, UInt, UserId,
KeyDerivationAlgorithm as RumaKeyDerivationAlgorithm, MatrixToUri, MatrixUri as RumaMatrixUri,
OwnedRoomId, OwnedUserId, UInt, UserId,
};
use tracing::info;
use crate::{
error::{ClientError, MediaInfoError},
helpers::unwrap_or_clone_arc,
notification_settings::{Action, PushCondition},
timeline::MessageContent,
utils::u64_to_uint,
};
@@ -985,3 +1018,591 @@ pub fn content_without_relation_from_message(
let msg_type = message.msg_type.try_into()?;
Ok(Arc::new(RoomMessageEventContentWithoutRelation::new(msg_type)))
}
/// Types of global account data events.
#[derive(Clone, uniffi::Enum)]
pub enum AccountDataEventType {
/// m.direct
Direct,
/// m.identity_server
IdentityServer,
/// m.ignored_user_list
IgnoredUserList,
/// m.push_rules
PushRules,
/// m.secret_storage.default_key
SecretStorageDefaultKey,
/// m.secret_storage.key.*
SecretStorageKey(String),
}
impl TryFrom<RumaGlobalAccountDataEventType> for AccountDataEventType {
type Error = String;
fn try_from(value: RumaGlobalAccountDataEventType) -> Result<Self, Self::Error> {
match value {
RumaGlobalAccountDataEventType::Direct => Ok(Self::Direct),
RumaGlobalAccountDataEventType::IdentityServer => Ok(Self::IdentityServer),
RumaGlobalAccountDataEventType::IgnoredUserList => Ok(Self::IgnoredUserList),
RumaGlobalAccountDataEventType::PushRules => Ok(Self::PushRules),
RumaGlobalAccountDataEventType::SecretStorageDefaultKey => {
Ok(Self::SecretStorageDefaultKey)
}
RumaGlobalAccountDataEventType::SecretStorageKey(key_id) => {
Ok(Self::SecretStorageKey(key_id))
}
_ => Err("Unsupported account data event type".to_owned()),
}
}
}
/// Global account data events.
#[derive(Clone, uniffi::Enum)]
pub enum AccountDataEvent {
/// m.direct
Direct {
/// The mapping of user ID to a list of room IDs of the direct rooms
/// for that user ID.
map: HashMap<String, Vec<String>>,
},
/// m.identity_server
IdentityServer {
/// The base URL for the identity server for client-server connections.
base_url: Option<String>,
},
/// m.ignored_user_list
IgnoredUserList {
/// The map of users to ignore. This is a mapping of user ID to empty
/// object.
ignored_users: HashMap<String, IgnoredUser>,
},
/// m.push_rules
PushRules {
/// The global ruleset.
global: Ruleset,
},
/// m.secret_storage.default_key
SecretStorageDefaultKey {
/// The ID of the default key.
key_id: String,
},
/// m.secret_storage.key.*
SecretStorageKey {
/// The ID of the key.
key_id: String,
/// The name of the key.
name: Option<String>,
/// The encryption algorithm used for this key.
///
/// Currently, only `m.secret_storage.v1.aes-hmac-sha2` is supported.
algorithm: SecretStorageEncryptionAlgorithm,
/// The passphrase from which to generate the key.
passphrase: Option<PassPhrase>,
},
}
/// Details about an ignored user.
///
/// This is currently empty.
#[derive(Clone, uniffi::Record)]
pub struct IgnoredUser {}
impl From<RumaIgnoredUser> for IgnoredUser {
fn from(_value: RumaIgnoredUser) -> Self {
IgnoredUser {}
}
}
/// A push ruleset scopes a set of rules according to some criteria.
#[derive(Clone, uniffi::Record)]
pub struct Ruleset {
/// These rules configure behavior for (unencrypted) messages that match
/// certain patterns.
pub content: Vec<PatternedPushRule>,
/// These user-configured rules are given the highest priority.
///
/// This field is named `override_` instead of `override` because the latter
/// is a reserved keyword in Rust.
pub override_: Vec<ConditionalPushRule>,
/// These rules change the behavior of all messages for a given room.
pub room: Vec<SimplePushRule>,
/// These rules configure notification behavior for messages from a specific
/// Matrix user ID.
pub sender: Vec<SimplePushRule>,
/// These rules are identical to override rules, but have a lower priority
/// than `content`, `room` and `sender` rules.
pub underride: Vec<ConditionalPushRule>,
}
impl TryFrom<RumaRuleset> for Ruleset {
type Error = String;
fn try_from(value: RumaRuleset) -> Result<Self, Self::Error> {
Ok(Self {
content: value
.content
.into_iter()
.map(TryInto::try_into)
.collect::<Result<Vec<_>, _>>()?,
override_: value
.override_
.into_iter()
.map(TryInto::try_into)
.collect::<Result<Vec<_>, _>>()?,
room: value.room.into_iter().map(TryInto::try_into).collect::<Result<Vec<_>, _>>()?,
sender: value
.sender
.into_iter()
.map(TryInto::try_into)
.collect::<Result<Vec<_>, _>>()?,
underride: value
.underride
.into_iter()
.map(TryInto::try_into)
.collect::<Result<Vec<_>, _>>()?,
})
}
}
/// Like [`SimplePushRule`], but with an additional `pattern`` field.
#[derive(Clone, uniffi::Record)]
pub struct PatternedPushRule {
/// Actions to determine if and how a notification is delivered for events
/// matching this rule.
pub actions: Vec<Action>,
/// Whether this is a default rule, or has been set explicitly.
pub default: bool,
/// Whether the push rule is enabled or not.
pub enabled: bool,
/// The ID of this rule.
pub rule_id: String,
/// The glob-style pattern to match against.
pub pattern: String,
}
impl TryFrom<RumaPatternedPushRule> for PatternedPushRule {
type Error = String;
fn try_from(value: RumaPatternedPushRule) -> Result<Self, Self::Error> {
Ok(Self {
actions: value
.actions
.into_iter()
.map(TryInto::try_into)
.collect::<Result<Vec<_>, _>>()?,
default: value.default,
enabled: value.enabled,
rule_id: value.rule_id,
pattern: value.pattern,
})
}
}
/// Like [`SimplePushRule`], but with an additional `conditions` field.
#[derive(Clone, uniffi::Record)]
pub struct ConditionalPushRule {
/// Actions to determine if and how a notification is delivered for events
/// matching this rule.
pub actions: Vec<Action>,
/// Whether this is a default rule, or has been set explicitly.
pub default: bool,
/// Whether the push rule is enabled or not.
pub enabled: bool,
/// The ID of this rule.
pub rule_id: String,
/// The conditions that must hold true for an event in order for a rule to
/// be applied to an event.
///
/// A rule with no conditions always matches.
pub conditions: Vec<PushCondition>,
}
impl TryFrom<RumaConditionalPushRule> for ConditionalPushRule {
type Error = String;
fn try_from(value: RumaConditionalPushRule) -> Result<Self, Self::Error> {
Ok(Self {
actions: value
.actions
.into_iter()
.map(TryInto::try_into)
.collect::<Result<Vec<_>, _>>()?,
default: value.default,
enabled: value.enabled,
rule_id: value.rule_id,
conditions: value
.conditions
.into_iter()
.map(TryInto::try_into)
.collect::<Result<Vec<_>, _>>()?,
})
}
}
/// A push rule is a single rule that states under what conditions an event
/// should be passed onto a push gateway and how the notification should be
/// presented.
#[derive(Clone, uniffi::Record)]
pub struct SimplePushRule {
/// Actions to determine if and how a notification is delivered for events
/// matching this rule.
pub actions: Vec<Action>,
/// Whether this is a default rule, or has been set explicitly.
pub default: bool,
/// Whether the push rule is enabled or not.
pub enabled: bool,
/// The ID of this rule.
///
/// This is generally the Matrix ID of the entity that it applies to.
pub rule_id: String,
}
impl TryFrom<RumaSimplePushRule<OwnedRoomId>> for SimplePushRule {
type Error = String;
fn try_from(value: RumaSimplePushRule<OwnedRoomId>) -> Result<Self, Self::Error> {
Ok(Self {
actions: value
.actions
.into_iter()
.map(TryInto::try_into)
.collect::<Result<Vec<_>, _>>()?,
default: value.default,
enabled: value.enabled,
rule_id: value.rule_id.into(),
})
}
}
impl TryFrom<RumaSimplePushRule<OwnedUserId>> for SimplePushRule {
type Error = String;
fn try_from(value: RumaSimplePushRule<OwnedUserId>) -> Result<Self, Self::Error> {
Ok(Self {
actions: value
.actions
.into_iter()
.map(TryInto::try_into)
.collect::<Result<Vec<_>, _>>()?,
default: value.default,
enabled: value.enabled,
rule_id: value.rule_id.into(),
})
}
}
/// An algorithm and its properties, used to encrypt a secret.
#[derive(Clone, uniffi::Enum)]
pub enum SecretStorageEncryptionAlgorithm {
/// Encrypted using the `m.secret_storage.v1.aes-hmac-sha2` algorithm.
///
/// Secrets using this method are encrypted using AES-CTR-256 and
/// authenticated using HMAC-SHA-256.
V1AesHmacSha2(SecretStorageV1AesHmacSha2Properties),
}
impl TryFrom<RumaSecretStorageEncryptionAlgorithm> for SecretStorageEncryptionAlgorithm {
type Error = String;
fn try_from(value: RumaSecretStorageEncryptionAlgorithm) -> Result<Self, Self::Error> {
match value {
RumaSecretStorageEncryptionAlgorithm::V1AesHmacSha2(properties) => {
Ok(Self::V1AesHmacSha2(properties.into()))
}
_ => Err("Unsupported encryption algorithm".to_owned()),
}
}
}
/// The key properties for the `m.secret_storage.v1.aes-hmac-sha2`` algorithm.
#[derive(Clone, uniffi::Record)]
pub struct SecretStorageV1AesHmacSha2Properties {
/// The 16-byte initialization vector, encoded as base64.
pub iv: Option<String>,
/// The MAC, encoded as base64.
pub mac: Option<String>,
}
impl From<RumaSecretStorageV1AesHmacSha2Properties> for SecretStorageV1AesHmacSha2Properties {
fn from(value: RumaSecretStorageV1AesHmacSha2Properties) -> Self {
Self {
iv: value.iv.map(|base64| base64.encode()),
mac: value.mac.map(|base64| base64.encode()),
}
}
}
/// A passphrase from which a key is to be derived.
#[derive(Clone, uniffi::Record)]
pub struct PassPhrase {
/// The algorithm to use to generate the key from the passphrase.
///
/// Must be `m.pbkdf2`.
pub algorithm: KeyDerivationAlgorithm,
/// The salt used in PBKDF2.
pub salt: String,
/// The number of iterations to use in PBKDF2.
pub iterations: u64,
/// The number of bits to generate for the key.
///
/// Defaults to 256
pub bits: u64,
}
impl TryFrom<RumaPassPhrase> for PassPhrase {
type Error = String;
fn try_from(value: RumaPassPhrase) -> Result<Self, Self::Error> {
Ok(PassPhrase {
algorithm: value.algorithm.try_into()?,
salt: value.salt,
iterations: value.iterations.into(),
bits: value.bits.into(),
})
}
}
/// A key algorithm to be used to generate a key from a passphrase.
#[derive(Clone, uniffi::Enum)]
pub enum KeyDerivationAlgorithm {
/// PBKDF2
Pbkfd2,
}
impl TryFrom<RumaKeyDerivationAlgorithm> for KeyDerivationAlgorithm {
type Error = String;
fn try_from(value: RumaKeyDerivationAlgorithm) -> Result<Self, Self::Error> {
match value {
RumaKeyDerivationAlgorithm::Pbkfd2 => Ok(Self::Pbkfd2),
_ => Err("Unsupported key derivation algorithm".to_owned()),
}
}
}
impl From<RumaGlobalAccountDataEvent<DirectEventContent>> for AccountDataEvent {
fn from(value: RumaGlobalAccountDataEvent<DirectEventContent>) -> Self {
Self::Direct {
map: value
.content
.0
.into_iter()
.map(|(user_id, room_ids)| {
(user_id.to_string(), room_ids.iter().map(ToString::to_string).collect())
})
.collect(),
}
}
}
impl From<RumaGlobalAccountDataEvent<IdentityServerEventContent>> for AccountDataEvent {
fn from(value: RumaGlobalAccountDataEvent<IdentityServerEventContent>) -> Self {
Self::IdentityServer { base_url: value.content.base_url.into_option() }
}
}
impl From<RumaGlobalAccountDataEvent<IgnoredUserListEventContent>> for AccountDataEvent {
fn from(value: RumaGlobalAccountDataEvent<IgnoredUserListEventContent>) -> Self {
Self::IgnoredUserList {
ignored_users: value
.content
.ignored_users
.into_iter()
.map(|(user_id, ignored_user)| {
(user_id.to_string(), IgnoredUser::from(ignored_user))
})
.collect(),
}
}
}
impl TryFrom<RumaGlobalAccountDataEvent<PushRulesEventContent>> for AccountDataEvent {
type Error = String;
fn try_from(
value: RumaGlobalAccountDataEvent<PushRulesEventContent>,
) -> Result<Self, Self::Error> {
Ok(Self::PushRules { global: value.content.global.try_into()? })
}
}
impl From<RumaGlobalAccountDataEvent<SecretStorageDefaultKeyEventContent>> for AccountDataEvent {
fn from(value: RumaGlobalAccountDataEvent<SecretStorageDefaultKeyEventContent>) -> Self {
Self::SecretStorageDefaultKey { key_id: value.content.key_id }
}
}
impl TryFrom<RumaGlobalAccountDataEvent<SecretStorageKeyEventContent>> for AccountDataEvent {
type Error = String;
fn try_from(
value: RumaGlobalAccountDataEvent<SecretStorageKeyEventContent>,
) -> Result<Self, Self::Error> {
Ok(Self::SecretStorageKey {
key_id: value.content.key_id,
name: value.content.name,
algorithm: value.content.algorithm.try_into()?,
passphrase: value.content.passphrase.map(TryInto::try_into).transpose()?,
})
}
}
/// Types of room account data events.
#[derive(Clone, uniffi::Enum)]
pub enum RoomAccountDataEventType {
/// m.fully_read
FullyRead,
/// m.marked_unread
MarkedUnread,
/// m.tag
Tag,
/// com.famedly.marked_unread
UnstableMarkedUnread,
}
impl TryFrom<RumaRoomAccountDataEventType> for RoomAccountDataEventType {
type Error = String;
fn try_from(value: RumaRoomAccountDataEventType) -> Result<Self, Self::Error> {
match value {
RumaRoomAccountDataEventType::FullyRead => Ok(Self::FullyRead),
RumaRoomAccountDataEventType::MarkedUnread => Ok(Self::MarkedUnread),
RumaRoomAccountDataEventType::Tag => Ok(Self::Tag),
RumaRoomAccountDataEventType::UnstableMarkedUnread => Ok(Self::UnstableMarkedUnread),
_ => Err("Unsupported account data event type".to_owned()),
}
}
}
/// Room account data events.
#[derive(Clone, uniffi::Enum)]
pub enum RoomAccountDataEvent {
/// m.fully_read
FullyReadEvent {
/// The event the user's read marker is located at in the room.
event_id: String,
},
/// m.marked_unread
MarkedUnread {
/// The current unread state.
unread: bool,
},
/// m.tag
Tag { tags: HashMap<TagName, TagInfo> },
/// com.famedly.marked_unread
UnstableMarkedUnread {
/// The current unread state.
unread: bool,
},
}
/// The name of a tag.
#[derive(Clone, PartialEq, Eq, Hash, uniffi::Enum)]
pub enum TagName {
/// `m.favourite`: The user's favorite rooms.
Favorite,
/// `m.lowpriority`: These should be shown with lower precedence than
/// others.
LowPriority,
/// `m.server_notice`: Used to identify
ServerNotice,
/// `u.*`: User-defined tag
User(UserTagName),
}
impl TryFrom<RumaTagName> for TagName {
type Error = String;
fn try_from(value: RumaTagName) -> Result<Self, Self::Error> {
match value {
RumaTagName::Favorite => Ok(Self::Favorite),
RumaTagName::LowPriority => Ok(Self::LowPriority),
RumaTagName::ServerNotice => Ok(Self::ServerNotice),
RumaTagName::User(name) => Ok(Self::User(name.into())),
_ => Err("Unsupported tag name".to_owned()),
}
}
}
/// A user-defined tag name.
#[derive(Clone, PartialEq, Eq, Hash, uniffi::Record)]
pub struct UserTagName {
name: String,
}
impl From<RumaUserTagName> for UserTagName {
fn from(value: RumaUserTagName) -> Self {
Self { name: value.as_ref().to_owned() }
}
}
/// Information about a tag.
#[derive(Clone, uniffi::Record)]
pub struct TagInfo {
/// Value to use for lexicographically ordering rooms with this tag.
pub order: Option<f64>,
}
impl From<RumaTagInfo> for TagInfo {
fn from(value: RumaTagInfo) -> Self {
Self { order: value.order }
}
}
impl From<RumaRoomAccountDataEvent<FullyReadEventContent>> for RoomAccountDataEvent {
fn from(value: RumaRoomAccountDataEvent<FullyReadEventContent>) -> Self {
Self::FullyReadEvent { event_id: value.content.event_id.into() }
}
}
impl From<RumaRoomAccountDataEvent<MarkedUnreadEventContent>> for RoomAccountDataEvent {
fn from(value: RumaRoomAccountDataEvent<MarkedUnreadEventContent>) -> Self {
Self::MarkedUnread { unread: value.content.unread }
}
}
impl TryFrom<RumaRoomAccountDataEvent<TagEventContent>> for RoomAccountDataEvent {
type Error = String;
fn try_from(value: RumaRoomAccountDataEvent<TagEventContent>) -> Result<Self, Self::Error> {
Ok(Self::Tag {
tags: value
.content
.tags
.into_iter()
.map(|(name, info)| name.try_into().map(|name| (name, info.into())))
.collect::<Result<HashMap<TagName, _>, _>>()?,
})
}
}
impl From<RumaRoomAccountDataEvent<UnstableMarkedUnreadEventContent>> for RoomAccountDataEvent {
fn from(value: RumaRoomAccountDataEvent<UnstableMarkedUnreadEventContent>) -> Self {
Self::UnstableMarkedUnread { unread: value.content.unread }
}
}