@@ -36,17 +36,18 @@ use itertools::Itertools;
3636use matrix_sdk:: {
3737 Client , Error as SDKError , deserialized_responses:: SyncOrStrippedState , executor:: AbortOnDrop ,
3838} ;
39+ use matrix_sdk_base:: deserialized_responses:: RawSyncOrStrippedState ;
3940use matrix_sdk_common:: executor:: spawn;
4041use ruma:: {
4142 OwnedRoomId , RoomId ,
4243 events:: {
43- self , SyncStateEvent ,
44+ self , StateEventType , SyncStateEvent ,
4445 space:: { child:: SpaceChildEventContent , parent:: SpaceParentEventContent } ,
4546 } ,
4647} ;
4748use thiserror:: Error ;
4849use tokio:: sync:: Mutex as AsyncMutex ;
49- use tracing:: error;
50+ use tracing:: { error, warn } ;
5051
5152use crate :: spaces:: { graph:: SpaceGraph , leave:: LeaveSpaceHandle } ;
5253pub 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