Skip to content

Commit 354af15

Browse files
committed
spaces: Add methods to add/remove space children.
1 parent 9ab886f commit 354af15

File tree

2 files changed

+145
-50
lines changed

2 files changed

+145
-50
lines changed

bindings/matrix-sdk-ffi/src/spaces.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,28 @@ impl SpaceService {
8787
Ok(Arc::new(SpaceRoomList::new(self.inner.space_room_list(space_id).await)))
8888
}
8989

90+
pub async fn add_child_to_space(
91+
&self,
92+
child_id: String,
93+
space_id: String,
94+
) -> Result<(), ClientError> {
95+
let space_id = RoomId::parse(space_id)?;
96+
let child_id = RoomId::parse(child_id)?;
97+
98+
self.inner.add_child_to_space(child_id, space_id).await.map_err(ClientError::from)
99+
}
100+
101+
pub async fn remove_child_from_space(
102+
&self,
103+
child_id: String,
104+
space_id: String,
105+
) -> Result<(), ClientError> {
106+
let space_id = RoomId::parse(space_id)?;
107+
let child_id = RoomId::parse(child_id)?;
108+
109+
self.inner.remove_child_from_space(child_id, space_id).await.map_err(ClientError::from)
110+
}
111+
90112
/// Start a space leave process returning a [`LeaveSpaceHandle`] from which
91113
/// rooms can be retrieved in reversed BFS order starting from the requested
92114
/// `space_id` graph node. If the room is unknown then an error will be

crates/matrix-sdk-ui/src/spaces/mod.rs

Lines changed: 123 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,18 @@ use itertools::Itertools;
3636
use matrix_sdk::{
3737
Client, Error as SDKError, deserialized_responses::SyncOrStrippedState, executor::AbortOnDrop,
3838
};
39+
use matrix_sdk_base::deserialized_responses::RawSyncOrStrippedState;
3940
use matrix_sdk_common::executor::spawn;
4041
use ruma::{
4142
OwnedRoomId, RoomId,
4243
events::{
43-
self, SyncStateEvent,
44+
self, StateEventType, SyncStateEvent,
4445
space::{child::SpaceChildEventContent, parent::SpaceParentEventContent},
4546
},
4647
};
4748
use thiserror::Error;
4849
use tokio::sync::Mutex as AsyncMutex;
49-
use tracing::error;
50+
use tracing::{error, warn};
5051

5152
use crate::spaces::{graph::SpaceGraph, leave::LeaveSpaceHandle};
5253
pub use crate::spaces::{room::SpaceRoom, room_list::SpaceRoomList};
@@ -63,6 +64,14 @@ pub enum Error {
6364
#[error("Room `{0}` not found")]
6465
RoomNotFound(OwnedRoomId),
6566

67+
/// The space parent/child state was missing.
68+
#[error("Missing `{0}` for `{1}`")]
69+
MissingState(StateEventType, OwnedRoomId),
70+
71+
/// The space parent/child state was missing.
72+
#[error("Failed updating space parent/child relationship")]
73+
UpdateRelationship(SDKError),
74+
6675
/// Failed to leave a space.
6776
#[error("Failed to leave space")]
6877
LeaveSpace(SDKError),
@@ -204,6 +213,91 @@ impl SpaceService {
204213
SpaceRoomList::new(self.client.clone(), space_id).await
205214
}
206215

216+
pub async fn add_child_to_space(
217+
&self,
218+
child_id: OwnedRoomId,
219+
space_id: OwnedRoomId,
220+
) -> Result<(), Error> {
221+
let Some(space_room) = self.client.get_room(&space_id) else {
222+
return Err(Error::RoomNotFound(space_id.to_owned()));
223+
};
224+
let Some(child_room) = self.client.get_room(&child_id) else {
225+
return Err(Error::RoomNotFound(child_id.to_owned()));
226+
};
227+
228+
let child_route =
229+
child_room.route().await.map_err(|error| Error::UpdateRelationship(error))?;
230+
space_room
231+
.send_state_event_for_key(&child_id, SpaceChildEventContent::new(child_route))
232+
.await
233+
.map_err(|error| Error::UpdateRelationship(error))?;
234+
235+
if let Ok(parent_route) =
236+
space_room.route().await.map_err(|error| Error::UpdateRelationship(error))
237+
&& let Err(error) = child_room
238+
.send_state_event_for_key(&space_id, SpaceParentEventContent::new(parent_route))
239+
.await
240+
{
241+
// As long as the space child is set, the room's parent is a best effort.
242+
warn!("Failed to set space parent event on the child: {error}");
243+
}
244+
245+
Ok(())
246+
}
247+
248+
pub async fn remove_child_from_space(
249+
&self,
250+
child_id: OwnedRoomId,
251+
space_id: OwnedRoomId,
252+
) -> Result<(), Error> {
253+
let Some(space_room) = self.client.get_room(&space_id) else {
254+
return Err(Error::RoomNotFound(space_id.to_owned()));
255+
};
256+
let Some(child_room) = self.client.get_room(&child_id) else {
257+
return Err(Error::RoomNotFound(child_id.to_owned()));
258+
};
259+
260+
let Ok(child_event) =
261+
space_room.get_state_event_static_for_key::<SpaceChildEventContent, _>(&child_id).await
262+
else {
263+
return Err(Error::MissingState(StateEventType::SpaceChild, child_id));
264+
};
265+
if let Some(RawSyncOrStrippedState::Sync(raw_event)) = child_event {
266+
match raw_event.deserialize() {
267+
Ok(event) => {
268+
space_room.redact(event.event_id(), None, None).await.map_err(|error| {
269+
Error::UpdateRelationship(matrix_sdk::Error::Http(Box::new(error)))
270+
})?;
271+
}
272+
Err(error) => {
273+
warn!("Failed to deserialize space child event: {error}");
274+
}
275+
}
276+
}
277+
278+
let Ok(parent_event) = child_room
279+
.get_state_event_static_for_key::<SpaceParentEventContent, _>(&space_id)
280+
.await
281+
else {
282+
return Err(Error::MissingState(StateEventType::SpaceParent, space_id));
283+
};
284+
285+
if let Some(RawSyncOrStrippedState::Sync(raw_event)) = parent_event {
286+
match raw_event.deserialize() {
287+
Ok(event) => {
288+
child_room.redact(event.event_id(), None, None).await.map_err(|error| {
289+
Error::UpdateRelationship(matrix_sdk::Error::Http(Box::new(error)))
290+
})?;
291+
}
292+
Err(error) => {
293+
warn!("Failed to deserialize space parent event: {error}");
294+
}
295+
}
296+
}
297+
298+
Ok(())
299+
}
300+
207301
/// Start a space leave process returning a [`LeaveSpaceHandle`] from which
208302
/// rooms can be retrieved in reversed BFS order starting from the requested
209303
/// `space_id` graph node. If the room is unknown then an error will be
@@ -500,10 +594,10 @@ mod tests {
500594
vec![SpaceRoom::new_from_known(&client.get_room(first_space_id).unwrap(), 0)].into()
501595
);
502596

503-
assert_eq!(
504-
space_service.joined_spaces().await,
505-
vec![SpaceRoom::new_from_known(&client.get_room(first_space_id).unwrap(), 0)]
506-
);
597+
assert_eq!(space_service.joined_spaces().await, vec![SpaceRoom::new_from_known(
598+
&client.get_room(first_space_id).unwrap(),
599+
0
600+
)]);
507601

508602
// And the stream is still pending as the initial values were
509603
// already set.
@@ -528,44 +622,26 @@ mod tests {
528622
.await;
529623

530624
// And expect the list to update
531-
assert_eq!(
532-
space_service.joined_spaces().await,
533-
vec![
625+
assert_eq!(space_service.joined_spaces().await, vec![
626+
SpaceRoom::new_from_known(&client.get_room(first_space_id).unwrap(), 0),
627+
SpaceRoom::new_from_known(&client.get_room(second_space_id).unwrap(), 1)
628+
]);
629+
630+
assert_next_eq!(joined_spaces_subscriber, vec![VectorDiff::Clear, VectorDiff::Append {
631+
values: vec![
534632
SpaceRoom::new_from_known(&client.get_room(first_space_id).unwrap(), 0),
535633
SpaceRoom::new_from_known(&client.get_room(second_space_id).unwrap(), 1)
536634
]
537-
);
538-
539-
assert_next_eq!(
540-
joined_spaces_subscriber,
541-
vec![
542-
VectorDiff::Clear,
543-
VectorDiff::Append {
544-
values: vec![
545-
SpaceRoom::new_from_known(&client.get_room(first_space_id).unwrap(), 0),
546-
SpaceRoom::new_from_known(&client.get_room(second_space_id).unwrap(), 1)
547-
]
548-
.into()
549-
},
550-
]
551-
);
635+
.into()
636+
},]);
552637

553638
server.sync_room(&client, LeftRoomBuilder::new(second_space_id)).await;
554639

555640
// and when one is left
556-
assert_next_eq!(
557-
joined_spaces_subscriber,
558-
vec![
559-
VectorDiff::Clear,
560-
VectorDiff::Append {
561-
values: vec![SpaceRoom::new_from_known(
562-
&client.get_room(first_space_id).unwrap(),
563-
0
564-
)]
565-
.into()
566-
},
567-
]
568-
);
641+
assert_next_eq!(joined_spaces_subscriber, vec![VectorDiff::Clear, VectorDiff::Append {
642+
values: vec![SpaceRoom::new_from_known(&client.get_room(first_space_id).unwrap(), 0)]
643+
.into()
644+
},]);
569645

570646
// but it doesn't when a non-space room gets joined
571647
server
@@ -578,10 +654,10 @@ mod tests {
578654

579655
// and the subscriber doesn't yield any updates
580656
assert_pending!(joined_spaces_subscriber);
581-
assert_eq!(
582-
space_service.joined_spaces().await,
583-
vec![SpaceRoom::new_from_known(&client.get_room(first_space_id).unwrap(), 0)]
584-
);
657+
assert_eq!(space_service.joined_spaces().await, vec![SpaceRoom::new_from_known(
658+
&client.get_room(first_space_id).unwrap(),
659+
0
660+
)]);
585661
}
586662

587663
#[async_test]
@@ -609,15 +685,12 @@ mod tests {
609685

610686
// Space with an `order` field set should come first in lexicographic
611687
// order and rest sorted by room ID.
612-
assert_eq!(
613-
space_service.joined_spaces().await,
614-
vec![
615-
SpaceRoom::new_from_known(&client.get_room(room_id!("!1:a.b")).unwrap(), 0),
616-
SpaceRoom::new_from_known(&client.get_room(room_id!("!2:a.b")).unwrap(), 0),
617-
SpaceRoom::new_from_known(&client.get_room(room_id!("!3:a.b")).unwrap(), 0),
618-
SpaceRoom::new_from_known(&client.get_room(room_id!("!4:a.b")).unwrap(), 0),
619-
]
620-
);
688+
assert_eq!(space_service.joined_spaces().await, vec![
689+
SpaceRoom::new_from_known(&client.get_room(room_id!("!1:a.b")).unwrap(), 0),
690+
SpaceRoom::new_from_known(&client.get_room(room_id!("!2:a.b")).unwrap(), 0),
691+
SpaceRoom::new_from_known(&client.get_room(room_id!("!3:a.b")).unwrap(), 0),
692+
SpaceRoom::new_from_known(&client.get_room(room_id!("!4:a.b")).unwrap(), 0),
693+
]);
621694
}
622695

623696
async fn add_space_rooms_with(

0 commit comments

Comments
 (0)