refactor(threads): flatten the ThreadStatus enum

This commit is contained in:
Benjamin Bouvier
2025-08-05 11:58:01 +02:00
parent d180d49c07
commit 0cdec9d912
10 changed files with 76 additions and 93 deletions
+12 -15
View File
@@ -1129,25 +1129,22 @@ impl Room {
pub async fn fetch_thread_subscription(
&self,
thread_root_event_id: String,
) -> Result<Option<ThreadStatus>, ClientError> {
) -> Result<Option<ThreadSubscription>, ClientError> {
let thread_root = EventId::parse(thread_root_event_id)?;
Ok(self.inner.fetch_thread_subscription(thread_root).await?.map(|sub| match sub {
matrix_sdk::room::ThreadStatus::Subscribed { automatic } => {
ThreadStatus::Subscribed { automatic }
}
}))
Ok(self
.inner
.fetch_thread_subscription(thread_root)
.await?
.map(|sub| ThreadSubscription { automatic: sub.automatic }))
}
}
/// Status of a thread subscription (MSC4306).
#[derive(uniffi::Enum)]
pub enum ThreadStatus {
/// The thread is subscribed to.
Subscribed {
/// Whether the thread subscription happened automatically (e.g. after a
/// mention) or if it was manually requested by the user.
automatic: bool,
},
/// A thread subscription (MSC4306).
#[derive(uniffi::Record)]
pub struct ThreadSubscription {
/// Whether the thread subscription happened automatically (e.g. after a
/// mention) or if it was manually requested by the user.
automatic: bool,
}
/// A listener for receiving new live location shares in a room.
@@ -48,7 +48,7 @@ use crate::{
deserialized_responses::MemberEvent,
store::{
ChildTransactionId, QueueWedgeError, Result, SerializableEventContent, StateStoreExt,
ThreadStatus,
ThreadSubscription,
},
};
@@ -1790,7 +1790,7 @@ impl StateStoreIntegrationTests for DynStateStore {
self.upsert_thread_subscription(
room_id(),
first_thread,
ThreadStatus::Subscribed { automatic: true },
ThreadSubscription { automatic: true },
)
.await
.unwrap();
@@ -1798,32 +1798,32 @@ impl StateStoreIntegrationTests for DynStateStore {
self.upsert_thread_subscription(
room_id(),
second_thread,
ThreadStatus::Subscribed { automatic: false },
ThreadSubscription { automatic: false },
)
.await
.unwrap();
// Now, reading the thread subscription returns the expected status.
let maybe_status = self.load_thread_subscription(room_id(), first_thread).await.unwrap();
assert_eq!(maybe_status, Some(ThreadStatus::Subscribed { automatic: true }));
assert_eq!(maybe_status, Some(ThreadSubscription { automatic: true }));
let maybe_status = self.load_thread_subscription(room_id(), second_thread).await.unwrap();
assert_eq!(maybe_status, Some(ThreadStatus::Subscribed { automatic: false }));
assert_eq!(maybe_status, Some(ThreadSubscription { automatic: false }));
// We can override the thread subscription status.
self.upsert_thread_subscription(
room_id(),
first_thread,
ThreadStatus::Subscribed { automatic: false },
ThreadSubscription { automatic: false },
)
.await
.unwrap();
// And it's correctly reflected.
let maybe_status = self.load_thread_subscription(room_id(), first_thread).await.unwrap();
assert_eq!(maybe_status, Some(ThreadStatus::Subscribed { automatic: false }));
assert_eq!(maybe_status, Some(ThreadSubscription { automatic: false }));
// And the second thread is still subscribed.
let maybe_status = self.load_thread_subscription(room_id(), second_thread).await.unwrap();
assert_eq!(maybe_status, Some(ThreadStatus::Subscribed { automatic: false }));
assert_eq!(maybe_status, Some(ThreadSubscription { automatic: false }));
// We can remove a thread subscription.
self.remove_thread_subscription(room_id(), second_thread).await.unwrap();
@@ -1833,7 +1833,7 @@ impl StateStoreIntegrationTests for DynStateStore {
assert_eq!(maybe_status, None);
// And the first thread is still subscribed.
let maybe_status = self.load_thread_subscription(room_id(), first_thread).await.unwrap();
assert_eq!(maybe_status, Some(ThreadStatus::Subscribed { automatic: false }));
assert_eq!(maybe_status, Some(ThreadSubscription { automatic: false }));
// Removing a thread subscription for an unknown thread is a no-op.
self.remove_thread_subscription(room_id(), second_thread).await.unwrap();
@@ -46,7 +46,7 @@ use super::{
use crate::{
MinimalRoomMemberEvent, RoomMemberships, StateStoreDataKey, StateStoreDataValue,
deserialized_responses::{DisplayName, RawAnySyncOrStrippedState},
store::{QueueWedgeError, ThreadStatus},
store::{QueueWedgeError, ThreadSubscription},
};
#[derive(Debug, Default)]
@@ -84,7 +84,7 @@ struct MemoryStoreInner {
send_queue_events: BTreeMap<OwnedRoomId, Vec<QueuedRequest>>,
dependent_send_queue_events: BTreeMap<OwnedRoomId, Vec<DependentQueuedRequest>>,
seen_knock_requests: BTreeMap<OwnedRoomId, BTreeMap<OwnedEventId, OwnedUserId>>,
thread_subscriptions: BTreeMap<OwnedRoomId, BTreeMap<OwnedEventId, ThreadStatus>>,
thread_subscriptions: BTreeMap<OwnedRoomId, BTreeMap<OwnedEventId, ThreadSubscription>>,
}
/// In-memory, non-persistent implementation of the `StateStore`.
@@ -958,7 +958,7 @@ impl StateStore for MemoryStore {
&self,
room: &RoomId,
thread_id: &EventId,
status: ThreadStatus,
subscription: ThreadSubscription,
) -> Result<(), Self::Error> {
self.inner
.write()
@@ -966,7 +966,7 @@ impl StateStore for MemoryStore {
.thread_subscriptions
.entry(room.to_owned())
.or_default()
.insert(thread_id.to_owned(), status);
.insert(thread_id.to_owned(), subscription);
Ok(())
}
@@ -974,7 +974,7 @@ impl StateStore for MemoryStore {
&self,
room: &RoomId,
thread_id: &EventId,
) -> Result<Option<ThreadStatus>, Self::Error> {
) -> Result<Option<ThreadSubscription>, Self::Error> {
let inner = self.inner.read().unwrap();
Ok(inner
.thread_subscriptions
+12 -23
View File
@@ -453,37 +453,26 @@ pub enum RoomLoadSettings {
One(OwnedRoomId),
}
/// Status of a thread subscription, as saved in the state store.
/// A thread subscription, as saved in the state store.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ThreadStatus {
/// The thread is subscribed to.
Subscribed {
/// Whether the subscription was made automatically by a client, not by
/// manual user choice.
automatic: bool,
},
pub struct ThreadSubscription {
/// Whether the subscription was made automatically by a client, not by
/// manual user choice.
pub automatic: bool,
}
impl ThreadStatus {
/// Convert the current [`ThreadStatus`] into a string representation.
impl ThreadSubscription {
/// Convert the current [`ThreadSubscription`] into a string representation.
pub fn as_str(&self) -> &'static str {
match self {
ThreadStatus::Subscribed { automatic } => {
if *automatic {
"automatic"
} else {
"manual"
}
}
}
if self.automatic { "automatic" } else { "manual" }
}
/// Convert a string representation into a [`ThreadStatus`], if it is a
/// valid one, or `None` otherwise.
/// Convert a string representation into a [`ThreadSubscription`], if it is
/// a valid one, or `None` otherwise.
pub fn from_value(s: &str) -> Option<Self> {
match s {
"automatic" => Some(ThreadStatus::Subscribed { automatic: true }),
"manual" => Some(ThreadStatus::Subscribed { automatic: false }),
"automatic" => Some(Self { automatic: true }),
"manual" => Some(Self { automatic: false }),
_ => None,
}
}
+6 -6
View File
@@ -55,7 +55,7 @@ use crate::{
deserialized_responses::{
DisplayName, RawAnySyncOrStrippedState, RawMemberEvent, RawSyncOrStrippedState,
},
store::ThreadStatus,
store::ThreadSubscription,
};
/// An abstract state store trait that can be used to implement different stores
@@ -485,7 +485,7 @@ pub trait StateStore: AsyncTraitDeps {
&self,
room: &RoomId,
thread_id: &EventId,
status: ThreadStatus,
subscription: ThreadSubscription,
) -> Result<(), Self::Error>;
/// Remove a previous thread subscription for a given room and thread.
@@ -504,7 +504,7 @@ pub trait StateStore: AsyncTraitDeps {
&self,
room: &RoomId,
thread_id: &EventId,
) -> Result<Option<ThreadStatus>, Self::Error>;
) -> Result<Option<ThreadSubscription>, Self::Error>;
}
#[repr(transparent)]
@@ -804,16 +804,16 @@ impl<T: StateStore> StateStore for EraseStateStoreError<T> {
&self,
room: &RoomId,
thread_id: &EventId,
status: ThreadStatus,
subscription: ThreadSubscription,
) -> Result<(), Self::Error> {
self.0.upsert_thread_subscription(room, thread_id, status).await.map_err(Into::into)
self.0.upsert_thread_subscription(room, thread_id, subscription).await.map_err(Into::into)
}
async fn load_thread_subscription(
&self,
room: &RoomId,
thread_id: &EventId,
) -> Result<Option<ThreadStatus>, Self::Error> {
) -> Result<Option<ThreadSubscription>, Self::Error> {
self.0.load_thread_subscription(room, thread_id).await.map_err(Into::into)
}
@@ -27,7 +27,8 @@ use matrix_sdk_base::{
store::{
ChildTransactionId, ComposerDraft, DependentQueuedRequest, DependentQueuedRequestKind,
QueuedRequest, QueuedRequestKind, RoomLoadSettings, SentRequestKey,
SerializableEventContent, ServerInfo, StateChanges, StateStore, StoreError, ThreadStatus,
SerializableEventContent, ServerInfo, StateChanges, StateStore, StoreError,
ThreadSubscription,
},
MinimalRoomMemberEvent, RoomInfo, RoomMemberships, StateStoreDataKey, StateStoreDataValue,
ROOM_VERSION_FALLBACK, ROOM_VERSION_RULES_FALLBACK,
@@ -1790,7 +1791,7 @@ impl_state_store!({
&self,
room: &RoomId,
thread_id: &EventId,
status: ThreadStatus,
subscription: ThreadSubscription,
) -> Result<()> {
let encoded_key = self.encode_key(keys::THREAD_SUBSCRIPTIONS, (room, thread_id));
@@ -1800,7 +1801,7 @@ impl_state_store!({
)?;
let obj = tx.object_store(keys::THREAD_SUBSCRIPTIONS)?;
let serialized_value = self.serialize_value(&status.as_str().to_owned());
let serialized_value = self.serialize_value(&subscription.as_str().to_owned());
obj.put_key_val(&encoded_key, &serialized_value?)?;
tx.await.into_result()?;
@@ -1812,7 +1813,7 @@ impl_state_store!({
&self,
room: &RoomId,
thread_id: &EventId,
) -> Result<Option<ThreadStatus>> {
) -> Result<Option<ThreadSubscription>> {
let encoded_key = self.encode_key(keys::THREAD_SUBSCRIPTIONS, (room, thread_id));
let js_value = self
@@ -1828,12 +1829,13 @@ impl_state_store!({
};
let status_string: String = self.deserialize_value(&js_value)?;
let status =
ThreadStatus::from_value(&status_string).ok_or_else(|| StoreError::InvalidData {
let status = ThreadSubscription::from_value(&status_string).ok_or_else(|| {
StoreError::InvalidData {
details: format!(
"invalid thread status for room {room} and thread {thread_id}: {status_string}"
),
})?;
}
})?;
Ok(Some(status))
}
+5 -5
View File
@@ -13,7 +13,7 @@ use matrix_sdk_base::{
store::{
migration_helpers::RoomInfoV1, ChildTransactionId, DependentQueuedRequest,
DependentQueuedRequestKind, QueueWedgeError, QueuedRequest, QueuedRequestKind,
RoomLoadSettings, SentRequestKey, ThreadStatus,
RoomLoadSettings, SentRequestKey, ThreadSubscription,
},
MinimalRoomMemberEvent, RoomInfo, RoomMemberships, RoomState, StateChanges, StateStore,
StateStoreDataKey, StateStoreDataValue, ROOM_VERSION_FALLBACK, ROOM_VERSION_RULES_FALLBACK,
@@ -2098,11 +2098,11 @@ impl StateStore for SqliteStateStore {
&self,
room_id: &RoomId,
thread_id: &EventId,
status: ThreadStatus,
subscription: ThreadSubscription,
) -> Result<(), Self::Error> {
let room_id = self.encode_key(keys::THREAD_SUBSCRIPTIONS, room_id);
let thread_id = self.encode_key(keys::THREAD_SUBSCRIPTIONS, thread_id);
let status = status.as_str();
let status = subscription.as_str();
self.acquire()
.await?
@@ -2121,7 +2121,7 @@ impl StateStore for SqliteStateStore {
&self,
room_id: &RoomId,
thread_id: &EventId,
) -> Result<Option<ThreadStatus>, Self::Error> {
) -> Result<Option<ThreadSubscription>, Self::Error> {
let room_id = self.encode_key(keys::THREAD_SUBSCRIPTIONS, room_id);
let thread_id = self.encode_key(keys::THREAD_SUBSCRIPTIONS, thread_id);
@@ -2136,7 +2136,7 @@ impl StateStore for SqliteStateStore {
.await
.optional()?
.map(|data| {
ThreadStatus::from_value(&data).ok_or_else(|| Error::InvalidData {
ThreadSubscription::from_value(&data).ok_or_else(|| Error::InvalidData {
details: format!("Invalid thread status: {data}"),
})
})
+9 -9
View File
@@ -34,7 +34,7 @@ use http::StatusCode;
pub use identity_status_changes::IdentityStatusChanges;
#[cfg(feature = "e2e-encryption")]
use matrix_sdk_base::crypto::{IdentityStatusChange, RoomIdentityProvider, UserIdentity};
pub use matrix_sdk_base::store::ThreadStatus;
pub use matrix_sdk_base::store::ThreadSubscription;
#[cfg(feature = "e2e-encryption")]
use matrix_sdk_base::{crypto::RoomEventDecryptionResult, deserialized_responses::EncryptionInfo};
use matrix_sdk_base::{
@@ -3700,7 +3700,7 @@ impl Room {
.upsert_thread_subscription(
self.room_id(),
&thread_root,
ThreadStatus::Subscribed { automatic: is_automatic },
ThreadSubscription { automatic: is_automatic },
)
.await?;
@@ -3761,8 +3761,8 @@ impl Room {
///
/// # Returns
///
/// - An `Ok` result with `Some(ThreadStatus)` if we have some subscription
/// information.
/// - An `Ok` result with `Some(ThreadSubscription)` if we have some
/// subscription information.
/// - An `Ok` result with `None` if the subscription does not exist, or the
/// event couldn't be found, or the event isn't a thread.
/// - An error if the request fails for any other reason, such as a network
@@ -3771,7 +3771,7 @@ impl Room {
pub async fn fetch_thread_subscription(
&self,
thread_root: OwnedEventId,
) -> Result<Option<ThreadStatus>> {
) -> Result<Option<ThreadSubscription>> {
let result = self
.client
.send(get_thread_subscription::unstable::Request::new(
@@ -3780,8 +3780,8 @@ impl Room {
))
.await;
let sub = match result {
Ok(response) => Some(ThreadStatus::Subscribed { automatic: response.automatic }),
let subscription = match result {
Ok(response) => Some(ThreadSubscription { automatic: response.automatic }),
Err(http_error) => match http_error.as_client_api_error() {
Some(error) if error.status_code == StatusCode::NOT_FOUND => None,
_ => return Err(http_error.into()),
@@ -3789,7 +3789,7 @@ impl Room {
};
// Keep the database in sync.
if let Some(sub) = &sub {
if let Some(sub) = &subscription {
self.client
.state_store()
.upsert_thread_subscription(self.room_id(), &thread_root, *sub)
@@ -3802,7 +3802,7 @@ impl Room {
.await?;
}
Ok(sub)
Ok(subscription)
}
}
@@ -1,5 +1,5 @@
use assert_matches2::assert_matches;
use matrix_sdk::{room::ThreadStatus, test_utils::mocks::MatrixMockServer};
use matrix_sdk::{room::ThreadSubscription, test_utils::mocks::MatrixMockServer};
use matrix_sdk_test::async_test;
use ruma::{owned_event_id, room_id};
@@ -34,9 +34,9 @@ async fn test_subscribe_thread() {
.mount()
.await;
// I can get the subscription status for that same thread.
// I can get the subscription for that same thread.
let subscription = room.fetch_thread_subscription(root_id.clone()).await.unwrap().unwrap();
assert_matches!(subscription, ThreadStatus::Subscribed { automatic: true });
assert_matches!(subscription, ThreadSubscription { automatic: true });
// If I try to get a subscription for a thread event that's unknown, I get no
// `ThreadSubscription`, not an error.
@@ -56,8 +56,8 @@ async fn test_subscribe_thread() {
room.unsubscribe_thread(root_id.clone()).await.unwrap();
// Now, if I retry to get the subscription status for this thread, it doesn't
// exist anymore.
// Now, if I retry to get the subscription for this thread, it doesn't exist
// anymore.
let subscription = room.fetch_thread_subscription(root_id.clone()).await.unwrap();
assert_matches!(subscription, None);
+4 -9
View File
@@ -13,7 +13,6 @@ use matrix_sdk::{
api::client::receipt::create_receipt::v3::ReceiptType,
events::room::message::RoomMessageEventContent,
},
store::ThreadStatus,
};
use matrix_sdk_ui::{
Timeline,
@@ -526,14 +525,10 @@ impl RoomView {
Ok(Some(subscription)) => {
status_handle.set_message(format!(
"Thread subscription status: {}",
match subscription {
ThreadStatus::Subscribed { automatic } => {
if automatic {
"subscribed (automatic)"
} else {
"subscribed (manual)"
}
}
if subscription.automatic {
"subscribed (automatic)"
} else {
"subscribed (manual)"
}
));
}