1use std::{cmp::Ordering, collections::HashMap, sync::Arc};
31
32use eyeball_im::{ObservableVector, VectorSubscriberBatchedStream};
33use futures_util::pin_mut;
34use imbl::Vector;
35use itertools::Itertools;
36use matrix_sdk::{
37 Client, Error as SDKError, Room, deserialized_responses::SyncOrStrippedState,
38 task_monitor::BackgroundTaskHandle,
39};
40use ruma::{
41 OwnedRoomId, RoomId,
42 events::{
43 self, StateEventType, SyncStateEvent,
44 space::{child::SpaceChildEventContent, parent::SpaceParentEventContent},
45 },
46};
47use thiserror::Error;
48use tokio::sync::Mutex as AsyncMutex;
49use tracing::{error, trace, warn};
50
51use crate::spaces::{graph::SpaceGraph, leave::LeaveSpaceHandle, room::SpaceRoomChildState};
52pub use crate::spaces::{room::SpaceRoom, room_list::SpaceRoomList};
53
54pub mod graph;
55pub mod leave;
56pub mod room;
57pub mod room_list;
58
59#[derive(Debug, Error)]
61pub enum Error {
62 #[error("User ID not available from client")]
64 UserIdNotFound,
65
66 #[error("Room `{0}` not found")]
68 RoomNotFound(OwnedRoomId),
69
70 #[error("Missing `{0}` for `{1}`")]
72 MissingState(StateEventType, OwnedRoomId),
73
74 #[error("Failed to set either of the m.space.parent or m.space.child state events")]
77 UpdateRelationship(SDKError),
78
79 #[error(
82 "Failed to set the expected m.space.parent state event (but any m.space.child changes were successful)"
83 )]
84 UpdateInverseRelationship(SDKError),
85
86 #[error("Failed to leave space")]
88 LeaveSpace(SDKError),
89
90 #[error("Failed to load members")]
92 LoadRoomMembers(SDKError),
93}
94
95struct SpaceState {
96 graph: SpaceGraph,
97 top_level_joined_spaces: ObservableVector<SpaceRoom>,
98 space_filters: ObservableVector<SpaceFilter>,
99}
100
101pub struct SpaceService {
141 client: Client,
142
143 space_state: Arc<AsyncMutex<SpaceState>>,
144
145 _room_update_handle: AsyncMutex<BackgroundTaskHandle>,
146}
147
148impl SpaceService {
149 pub async fn new(client: Client) -> Self {
151 let space_state = Arc::new(AsyncMutex::new(SpaceState {
152 graph: SpaceGraph::new(),
153 top_level_joined_spaces: ObservableVector::new(),
154 space_filters: ObservableVector::new(),
155 }));
156
157 let room_update_handle = client
158 .task_monitor()
159 .spawn_background_task("space_service", {
160 let client = client.clone();
161 let space_state = Arc::clone(&space_state);
162 let all_room_updates_receiver = client.subscribe_to_all_room_updates();
163
164 async move {
165 pin_mut!(all_room_updates_receiver);
166
167 loop {
168 match all_room_updates_receiver.recv().await {
169 Ok(updates) => {
170 if updates.is_empty() {
171 continue;
172 }
173
174 let (spaces, filters, graph) =
175 Self::build_space_state(&client).await;
176 Self::update_space_state_if_needed(
177 Vector::from(spaces),
178 Vector::from(filters),
179 graph,
180 &space_state,
181 )
182 .await;
183 }
184 Err(err) => {
185 error!("error when listening to room updates: {err}");
186 }
187 }
188 }
189 }
190 })
191 .abort_on_drop();
192
193 let (spaces, filters, graph) = Self::build_space_state(&client).await;
195 Self::update_space_state_if_needed(
196 Vector::from(spaces),
197 Vector::from(filters),
198 graph,
199 &space_state,
200 )
201 .await;
202
203 Self { client, space_state, _room_update_handle: AsyncMutex::new(room_update_handle) }
204 }
205
206 pub async fn subscribe_to_top_level_joined_spaces(
209 &self,
210 ) -> (Vector<SpaceRoom>, VectorSubscriberBatchedStream<SpaceRoom>) {
211 self.space_state
212 .lock()
213 .await
214 .top_level_joined_spaces
215 .subscribe()
216 .into_values_and_batched_stream()
217 }
218
219 pub async fn top_level_joined_spaces(&self) -> Vec<SpaceRoom> {
223 let (top_level_joined_spaces, filters, graph) = Self::build_space_state(&self.client).await;
224
225 Self::update_space_state_if_needed(
226 Vector::from(top_level_joined_spaces.clone()),
227 Vector::from(filters),
228 graph,
229 &self.space_state,
230 )
231 .await;
232
233 top_level_joined_spaces
234 }
235
236 pub async fn space_filters(&self) -> Vec<SpaceFilter> {
281 let (top_level_joined_spaces, filters, graph) = Self::build_space_state(&self.client).await;
282
283 Self::update_space_state_if_needed(
284 Vector::from(top_level_joined_spaces),
285 Vector::from(filters.clone()),
286 graph,
287 &self.space_state,
288 )
289 .await;
290
291 filters
292 }
293
294 pub async fn subscribe_to_space_filters(
296 &self,
297 ) -> (Vector<SpaceFilter>, VectorSubscriberBatchedStream<SpaceFilter>) {
298 self.space_state.lock().await.space_filters.subscribe().into_values_and_batched_stream()
299 }
300
301 pub async fn editable_spaces(&self) -> Vec<SpaceRoom> {
307 let Some(user_id) = self.client.user_id() else {
308 return vec![];
309 };
310
311 let graph = &self.space_state.lock().await.graph;
312 let rooms = self.client.joined_space_rooms();
313
314 let mut editable_spaces = Vec::new();
315 for room in &rooms {
316 if let Ok(power_levels) = room.power_levels().await
317 && power_levels.user_can_send_state(user_id, StateEventType::SpaceChild)
318 {
319 let room_id = room.room_id();
320 editable_spaces
321 .push(SpaceRoom::new_from_known(room, graph.children_of(room_id).len() as u64));
322 }
323 }
324
325 editable_spaces
326 }
327
328 pub async fn space_room_list(&self, space_id: OwnedRoomId) -> SpaceRoomList {
330 SpaceRoomList::new(self.client.clone(), space_id).await
331 }
332
333 pub async fn joined_parents_of_child(&self, child_id: &RoomId) -> Vec<SpaceRoom> {
335 let graph = &self.space_state.lock().await.graph;
336
337 graph
338 .parents_of(child_id)
339 .into_iter()
340 .filter_map(|parent_id| self.client.get_room(parent_id))
341 .map(|room| {
342 SpaceRoom::new_from_known(&room, graph.children_of(room.room_id()).len() as u64)
343 })
344 .collect()
345 }
346
347 pub async fn get_space_room(&self, room_id: &RoomId) -> Option<SpaceRoom> {
350 let graph = &self.space_state.lock().await.graph;
351
352 if graph.has_node(room_id)
353 && let Some(room) = self.client.get_room(room_id)
354 {
355 Some(SpaceRoom::new_from_known(&room, graph.children_of(room.room_id()).len() as u64))
356 } else {
357 None
358 }
359 }
360
361 pub async fn add_child_to_space(
362 &self,
363 child_id: OwnedRoomId,
364 space_id: OwnedRoomId,
365 ) -> Result<(), Error> {
366 let user_id = self.client.user_id().ok_or(Error::UserIdNotFound)?;
367 let space_room =
368 self.client.get_room(&space_id).ok_or(Error::RoomNotFound(space_id.to_owned()))?;
369 let child_room =
370 self.client.get_room(&child_id).ok_or(Error::RoomNotFound(child_id.to_owned()))?;
371 let child_power_levels = child_room
372 .power_levels()
373 .await
374 .map_err(|error| Error::UpdateRelationship(matrix_sdk::Error::from(error)))?;
375
376 let child_route = child_room.route().await.map_err(Error::UpdateRelationship)?;
378 space_room
379 .send_state_event_for_key(&child_id, SpaceChildEventContent::new(child_route))
380 .await
381 .map_err(Error::UpdateRelationship)?;
382
383 if child_power_levels.user_can_send_state(user_id, StateEventType::SpaceParent) {
385 let parent_route =
386 space_room.route().await.map_err(Error::UpdateInverseRelationship)?;
387 child_room
388 .send_state_event_for_key(&space_id, SpaceParentEventContent::new(parent_route))
389 .await
390 .map_err(Error::UpdateInverseRelationship)?;
391 } else {
392 warn!("The current user doesn't have permission to set the child's parent.");
393 }
394
395 Ok(())
396 }
397
398 pub async fn remove_child_from_space(
399 &self,
400 child_id: OwnedRoomId,
401 space_id: OwnedRoomId,
402 ) -> Result<(), Error> {
403 let user_id = self.client.user_id().ok_or(Error::UserIdNotFound)?;
404 let space_room =
405 self.client.get_room(&space_id).ok_or(Error::RoomNotFound(space_id.to_owned()))?;
406
407 if let Ok(Some(_)) =
408 space_room.get_state_event_static_for_key::<SpaceChildEventContent, _>(&child_id).await
409 {
410 space_room
417 .send_state_event_raw("m.space.child", child_id.as_str(), serde_json::json!({}))
418 .await
419 .map_err(Error::UpdateRelationship)?;
420 } else {
421 warn!("A space child event wasn't found on the parent, ignoring.");
422 }
423
424 if let Some(child_room) = self.client.get_room(&child_id) {
425 let power_levels = child_room.power_levels().await.map_err(|error| {
426 Error::UpdateInverseRelationship(matrix_sdk::Error::from(error))
427 })?;
428
429 if power_levels.user_can_send_state(user_id, StateEventType::SpaceParent)
430 && let Ok(Some(_)) = child_room
431 .get_state_event_static_for_key::<SpaceParentEventContent, _>(&space_id)
432 .await
433 {
434 child_room
436 .send_state_event_raw(
437 "m.space.parent",
438 space_id.as_str(),
439 serde_json::json!({}),
440 )
441 .await
442 .map_err(Error::UpdateInverseRelationship)?;
443 } else {
444 warn!("A space parent event wasn't found on the child, ignoring.");
445 }
446 } else {
447 warn!("The child room is unknown, skipping m.space.parent removal.");
448 }
449
450 Ok(())
451 }
452
453 pub async fn leave_space(&self, space_id: &RoomId) -> Result<LeaveSpaceHandle, Error> {
461 let space_state = self.space_state.lock().await;
462
463 if !space_state.graph.has_node(space_id) {
464 return Err(Error::RoomNotFound(space_id.to_owned()));
465 }
466
467 let room_ids = space_state.graph.flattened_bottom_up_subtree(space_id);
468
469 let handle = LeaveSpaceHandle::new(self.client.clone(), room_ids).await;
470
471 Ok(handle)
472 }
473
474 async fn update_space_state_if_needed(
475 new_spaces: Vector<SpaceRoom>,
476 new_filters: Vector<SpaceFilter>,
477 new_graph: SpaceGraph,
478 space_state: &Arc<AsyncMutex<SpaceState>>,
479 ) {
480 let mut space_state = space_state.lock().await;
481
482 if new_spaces != space_state.top_level_joined_spaces.clone() {
483 space_state.top_level_joined_spaces.clear();
484 space_state.top_level_joined_spaces.append(new_spaces);
485 }
486
487 if new_filters != space_state.space_filters.clone() {
488 space_state.space_filters.clear();
489 space_state.space_filters.append(new_filters);
490 }
491
492 space_state.graph = new_graph;
493 }
494
495 async fn build_space_state(client: &Client) -> (Vec<SpaceRoom>, Vec<SpaceFilter>, SpaceGraph) {
496 let joined_spaces = client.joined_space_rooms();
497
498 let mut graph = SpaceGraph::new();
500
501 let mut space_child_states = HashMap::<OwnedRoomId, SpaceRoomChildState>::new();
503
504 for space in joined_spaces.iter() {
507 graph.add_node(space.room_id().to_owned());
508
509 if let Ok(parents) = space.get_state_events_static::<SpaceParentEventContent>().await {
510 parents.into_iter()
511 .flat_map(|parent_event| match parent_event.deserialize() {
512 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Original(e))) => {
513 Some(e.state_key)
514 }
515 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Redacted(_))) => None,
516 Ok(SyncOrStrippedState::Stripped(e)) => Some(e.state_key),
517 Err(e) => {
518 trace!(room_id = ?space.room_id(), "Could not deserialize m.space.parent: {e}");
519 None
520 }
521 }).for_each(|parent| graph.add_edge(parent, space.room_id().to_owned()));
522 } else {
523 error!(room_id = ?space.room_id(), "Could not get m.space.parent events");
524 }
525
526 if let Ok(children) = space.get_state_events_static::<SpaceChildEventContent>().await {
527 children.into_iter()
528 .filter_map(|child_event| match child_event.deserialize() {
529 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Original(e))) => {
530 space_child_states.insert(
531 e.state_key.to_owned(),
532 SpaceRoomChildState {
533 order: e.content.order.clone(),
534 origin_server_ts: e.origin_server_ts,
535 },
536 );
537
538 Some(e.state_key)
539 }
540 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Redacted(_))) => None,
541 Ok(SyncOrStrippedState::Stripped(e)) => Some(e.state_key),
542 Err(e) => {
543 trace!(room_id = ?space.room_id(), "Could not deserialize m.space.child: {e}");
544 None
545 }
546 }).for_each(|child| graph.add_edge(space.room_id().to_owned(), child));
547 } else {
548 error!(room_id = ?space.room_id(), "Could not get m.space.child events");
549 }
550 }
551
552 graph.remove_cycles();
555
556 let root_nodes = graph.root_nodes();
557
558 let top_level_space_rooms = joined_spaces
562 .iter()
563 .filter(|room| root_nodes.contains(&room.room_id()))
564 .collect::<Vec<_>>();
565
566 let mut top_level_space_order = HashMap::new();
567 for space in &top_level_space_rooms {
568 if let Ok(Some(raw_event)) =
569 space.account_data_static::<events::space_order::SpaceOrderEventContent>().await
570 && let Ok(event) = raw_event.deserialize()
571 {
572 top_level_space_order.insert(space.room_id().to_owned(), event.content.order);
573 }
574 }
575
576 let top_level_space_rooms = top_level_space_rooms
577 .into_iter()
578 .sorted_by(|a, b| {
579 match (
581 top_level_space_order.get(a.room_id()),
582 top_level_space_order.get(b.room_id()),
583 ) {
584 (Some(a_order), Some(b_order)) => {
585 a_order.cmp(b_order).then(a.room_id().cmp(b.room_id()))
586 }
587 (Some(_), None) => Ordering::Less,
588 (None, Some(_)) => Ordering::Greater,
589 (None, None) => a.room_id().cmp(b.room_id()),
590 }
591 })
592 .collect::<Vec<_>>();
593
594 let top_level_spaces = top_level_space_rooms
595 .iter()
596 .map(|room| {
597 SpaceRoom::new_from_known(room, graph.children_of(room.room_id()).len() as u64)
598 })
599 .collect();
600
601 let space_filters =
602 Self::build_space_filters(client, &graph, top_level_space_rooms, space_child_states);
603
604 (top_level_spaces, space_filters, graph)
605 }
606
607 fn build_space_filters(
616 client: &Client,
617 graph: &SpaceGraph,
618 top_level_space_rooms: Vec<&Room>,
619 space_child_states: HashMap<OwnedRoomId, SpaceRoomChildState>,
620 ) -> Vec<SpaceFilter> {
621 let mut filters = Vec::new();
622 for top_level_space in top_level_space_rooms {
623 let children = graph
624 .children_of(top_level_space.room_id())
625 .into_iter()
626 .map(|id| id.to_owned())
627 .collect::<Vec<_>>();
628
629 filters.push(SpaceFilter {
630 space_room: SpaceRoom::new_from_known(top_level_space, children.len() as u64),
631 level: 0,
632 descendants: children.clone(),
633 });
634
635 filters.append(
636 &mut children
637 .iter()
638 .filter_map(|id| client.get_room(id))
639 .filter(|room| room.is_space())
640 .map(|room| {
641 SpaceRoom::new_from_known(
642 &room,
643 graph.children_of(room.room_id()).len() as u64,
644 )
645 })
646 .sorted_by(|a, b| {
647 let a_state = space_child_states.get(&a.room_id).cloned();
648 let b_state = space_child_states.get(&b.room_id).cloned();
649
650 SpaceRoom::compare_rooms(a, b, a_state, b_state)
651 })
652 .map(|space_room| {
653 let descendants = graph.flattened_bottom_up_subtree(&space_room.room_id);
654
655 SpaceFilter { space_room, level: 1, descendants }
656 })
657 .collect::<Vec<_>>(),
658 );
659 }
660
661 filters
662 }
663}
664
665#[derive(Debug, Clone, PartialEq)]
666pub struct SpaceFilter {
667 pub space_room: SpaceRoom,
669
670 pub level: u8,
673
674 pub descendants: Vec<OwnedRoomId>,
678}
679
680#[cfg(test)]
681mod tests {
682 use std::collections::BTreeMap;
683
684 use assert_matches2::assert_let;
685 use eyeball_im::VectorDiff;
686 use futures_util::{StreamExt, pin_mut};
687 use matrix_sdk::{room::ParentSpace, test_utils::mocks::MatrixMockServer};
688 use matrix_sdk_test::{
689 JoinedRoomBuilder, LeftRoomBuilder, async_test, event_factory::EventFactory,
690 };
691 use ruma::{
692 MilliSecondsSinceUnixEpoch, RoomVersionId, UserId, event_id, owned_room_id, room_id,
693 serde::Raw,
694 };
695 use serde_json::json;
696 use stream_assert::{assert_next_eq, assert_pending};
697
698 use super::*;
699
700 #[async_test]
701 async fn test_spaces_hierarchy() {
702 let server = MatrixMockServer::new().await;
703 let client = server.client_builder().build().await;
704 let user_id = client.user_id().unwrap();
705 let space_service = SpaceService::new(client.clone()).await;
706 let factory = EventFactory::new();
707
708 server.mock_room_state_encryption().plain().mount().await;
709
710 let parent_space_id = room_id!("!parent_space:example.org");
713 let child_space_id_1 = room_id!("!child_space_1:example.org");
714 let child_space_id_2 = room_id!("!child_space_2:example.org");
715
716 add_space_rooms(
717 vec![
718 MockSpaceRoomParameters {
719 room_id: child_space_id_1,
720 order: None,
721 parents: vec![parent_space_id],
722 children: vec![],
723 power_level: None,
724 },
725 MockSpaceRoomParameters {
726 room_id: child_space_id_2,
727 order: None,
728 parents: vec![parent_space_id],
729 children: vec![],
730 power_level: None,
731 },
732 MockSpaceRoomParameters {
733 room_id: parent_space_id,
734 order: None,
735 parents: vec![],
736 children: vec![child_space_id_1, child_space_id_2],
737 power_level: None,
738 },
739 ],
740 &client,
741 &server,
742 &factory,
743 user_id,
744 )
745 .await;
746
747 assert_eq!(
749 space_service
750 .top_level_joined_spaces()
751 .await
752 .iter()
753 .map(|s| s.room_id.to_owned())
754 .collect::<Vec<_>>(),
755 vec![parent_space_id]
756 );
757
758 assert_eq!(
760 space_service
761 .top_level_joined_spaces()
762 .await
763 .iter()
764 .map(|s| s.children_count)
765 .collect::<Vec<_>>(),
766 vec![2]
767 );
768
769 let parent_space = client.get_room(parent_space_id).unwrap();
770 assert!(parent_space.is_space());
771
772 let spaces: Vec<ParentSpace> = client
775 .get_room(child_space_id_1)
776 .unwrap()
777 .parent_spaces()
778 .await
779 .unwrap()
780 .map(Result::unwrap)
781 .collect()
782 .await;
783
784 assert_let!(ParentSpace::Reciprocal(parent) = spaces.first().unwrap());
785 assert_eq!(parent.room_id(), parent_space.room_id());
786
787 let spaces: Vec<ParentSpace> = client
788 .get_room(child_space_id_2)
789 .unwrap()
790 .parent_spaces()
791 .await
792 .unwrap()
793 .map(Result::unwrap)
794 .collect()
795 .await;
796
797 assert_let!(ParentSpace::Reciprocal(parent) = spaces.last().unwrap());
798 assert_eq!(parent.room_id(), parent_space.room_id());
799 }
800
801 #[async_test]
802 async fn test_joined_spaces_updates() {
803 let server = MatrixMockServer::new().await;
804 let client = server.client_builder().build().await;
805 let user_id = client.user_id().unwrap();
806 let factory = EventFactory::new();
807
808 server.mock_room_state_encryption().plain().mount().await;
809
810 let first_space_id = room_id!("!first_space:example.org");
811 let second_space_id = room_id!("!second_space:example.org");
812
813 server
815 .sync_room(
816 &client,
817 JoinedRoomBuilder::new(first_space_id)
818 .add_state_event(factory.create(user_id, RoomVersionId::V1).with_space_type()),
819 )
820 .await;
821
822 let space_service = SpaceService::new(client.clone()).await;
826
827 let (initial_values, joined_spaces_subscriber) =
828 space_service.subscribe_to_top_level_joined_spaces().await;
829 pin_mut!(joined_spaces_subscriber);
830 assert_pending!(joined_spaces_subscriber);
831
832 assert_eq!(
833 initial_values,
834 vec![SpaceRoom::new_from_known(&client.get_room(first_space_id).unwrap(), 0)].into()
835 );
836
837 assert_eq!(
838 space_service.top_level_joined_spaces().await,
839 vec![SpaceRoom::new_from_known(&client.get_room(first_space_id).unwrap(), 0)]
840 );
841
842 assert_pending!(joined_spaces_subscriber);
845
846 server
849 .sync_room(
850 &client,
851 JoinedRoomBuilder::new(second_space_id)
852 .add_state_event(factory.create(user_id, RoomVersionId::V1).with_space_type())
853 .add_state_event(
854 factory
855 .space_child(
856 second_space_id.to_owned(),
857 owned_room_id!("!child:example.org"),
858 )
859 .sender(user_id),
860 ),
861 )
862 .await;
863
864 assert_eq!(
866 space_service.top_level_joined_spaces().await,
867 vec![
868 SpaceRoom::new_from_known(&client.get_room(first_space_id).unwrap(), 0),
869 SpaceRoom::new_from_known(&client.get_room(second_space_id).unwrap(), 1)
870 ]
871 );
872
873 assert_next_eq!(
874 joined_spaces_subscriber,
875 vec![
876 VectorDiff::Clear,
877 VectorDiff::Append {
878 values: vec![
879 SpaceRoom::new_from_known(&client.get_room(first_space_id).unwrap(), 0),
880 SpaceRoom::new_from_known(&client.get_room(second_space_id).unwrap(), 1)
881 ]
882 .into()
883 },
884 ]
885 );
886
887 server.sync_room(&client, LeftRoomBuilder::new(second_space_id)).await;
888
889 assert_next_eq!(
891 joined_spaces_subscriber,
892 vec![
893 VectorDiff::Clear,
894 VectorDiff::Append {
895 values: vec![SpaceRoom::new_from_known(
896 &client.get_room(first_space_id).unwrap(),
897 0
898 )]
899 .into()
900 },
901 ]
902 );
903
904 server
906 .sync_room(
907 &client,
908 JoinedRoomBuilder::new(room_id!("!room:example.org"))
909 .add_state_event(factory.create(user_id, RoomVersionId::V1)),
910 )
911 .await;
912
913 assert_pending!(joined_spaces_subscriber);
915 assert_eq!(
916 space_service.top_level_joined_spaces().await,
917 vec![SpaceRoom::new_from_known(&client.get_room(first_space_id).unwrap(), 0)]
918 );
919 }
920
921 #[async_test]
922 async fn test_space_filters() {
923 let server = MatrixMockServer::new().await;
924 let client = server.client_builder().build().await;
925
926 server.mock_room_state_encryption().plain().mount().await;
927
928 add_space_rooms(
929 vec![
930 MockSpaceRoomParameters {
931 room_id: room_id!("!1:a.b"),
932 order: None,
933 parents: vec![],
934 children: vec![],
935 power_level: None,
936 },
937 MockSpaceRoomParameters {
938 room_id: room_id!("!1.2:a.b"),
939 order: None,
940 parents: vec![room_id!("!1:a.b")],
941 children: vec![],
942 power_level: None,
943 },
944 MockSpaceRoomParameters {
945 room_id: room_id!("!1.2.3:a.b"),
946 order: None,
947 parents: vec![room_id!("!1.2:a.b")],
948 children: vec![],
949 power_level: None,
950 },
951 MockSpaceRoomParameters {
952 room_id: room_id!("!1.2.3.4:a.b"),
953 order: None,
954 parents: vec![room_id!("!1.2.3:a.b")],
955 children: vec![],
956 power_level: None,
957 },
958 ],
959 &client,
960 &server,
961 &EventFactory::new(),
962 client.user_id().unwrap(),
963 )
964 .await;
965
966 let space_service = SpaceService::new(client.clone()).await;
967
968 let filters = space_service.space_filters().await;
969 assert_eq!(filters.len(), 2);
970 assert_eq!(filters[0].space_room.room_id, room_id!("!1:a.b"));
971 assert_eq!(filters[0].level, 0);
972 assert_eq!(filters[0].descendants.len(), 1); assert_eq!(filters[1].space_room.room_id, room_id!("!1.2:a.b"));
974 assert_eq!(filters[1].level, 1);
975 assert_eq!(filters[1].descendants.len(), 3);
976
977 let (initial_values, space_filters_subscriber) =
978 space_service.subscribe_to_space_filters().await;
979 pin_mut!(space_filters_subscriber);
980 assert_pending!(space_filters_subscriber);
981
982 assert_eq!(initial_values, filters.into());
983
984 add_space_rooms(
985 vec![MockSpaceRoomParameters {
986 room_id: room_id!("!1.2.3.4.5:a.b"),
987 order: None,
988 parents: vec![room_id!("!1.2.3.4:a.b")],
989 children: vec![],
990 power_level: None,
991 }],
992 &client,
993 &server,
994 &EventFactory::new(),
995 client.user_id().unwrap(),
996 )
997 .await;
998
999 space_filters_subscriber.next().await;
1000
1001 let filters = space_service.space_filters().await;
1002 assert_eq!(filters[0].descendants.len(), 1);
1003 assert_eq!(filters[1].descendants.len(), 4);
1004 }
1005
1006 #[async_test]
1007 async fn test_top_level_space_order() {
1008 let server = MatrixMockServer::new().await;
1009 let client = server.client_builder().build().await;
1010
1011 server.mock_room_state_encryption().plain().mount().await;
1012
1013 add_space_rooms(
1014 vec![
1015 MockSpaceRoomParameters {
1016 room_id: room_id!("!2:a.b"),
1017 order: Some("2"),
1018 parents: vec![],
1019 children: vec![],
1020 power_level: None,
1021 },
1022 MockSpaceRoomParameters {
1023 room_id: room_id!("!4:a.b"),
1024 order: None,
1025 parents: vec![],
1026 children: vec![],
1027 power_level: None,
1028 },
1029 MockSpaceRoomParameters {
1030 room_id: room_id!("!3:a.b"),
1031 order: None,
1032 parents: vec![],
1033 children: vec![],
1034 power_level: None,
1035 },
1036 MockSpaceRoomParameters {
1037 room_id: room_id!("!1:a.b"),
1038 order: Some("1"),
1039 parents: vec![],
1040 children: vec![],
1041 power_level: None,
1042 },
1043 ],
1044 &client,
1045 &server,
1046 &EventFactory::new(),
1047 client.user_id().unwrap(),
1048 )
1049 .await;
1050
1051 let space_service = SpaceService::new(client.clone()).await;
1052
1053 assert_eq!(
1056 space_service.top_level_joined_spaces().await,
1057 vec![
1058 SpaceRoom::new_from_known(&client.get_room(room_id!("!1:a.b")).unwrap(), 0),
1059 SpaceRoom::new_from_known(&client.get_room(room_id!("!2:a.b")).unwrap(), 0),
1060 SpaceRoom::new_from_known(&client.get_room(room_id!("!3:a.b")).unwrap(), 0),
1061 SpaceRoom::new_from_known(&client.get_room(room_id!("!4:a.b")).unwrap(), 0),
1062 ]
1063 );
1064 }
1065
1066 #[async_test]
1067 async fn test_editable_spaces() {
1068 let server = MatrixMockServer::new().await;
1070 let client = server.client_builder().build().await;
1071 let user_id = client.user_id().unwrap();
1072 let factory = EventFactory::new();
1073
1074 server.mock_room_state_encryption().plain().mount().await;
1075
1076 let admin_space_id = room_id!("!admin_space:example.org");
1077 let admin_subspace_id = room_id!("!admin_subspace:example.org");
1078 let regular_space_id = room_id!("!regular_space:example.org");
1079 let regular_subspace_id = room_id!("!regular_subspace:example.org");
1080
1081 add_space_rooms(
1082 vec![
1083 MockSpaceRoomParameters {
1084 room_id: admin_space_id,
1085 order: None,
1086 parents: vec![],
1087 children: vec![regular_subspace_id],
1088 power_level: Some(100),
1089 },
1090 MockSpaceRoomParameters {
1091 room_id: admin_subspace_id,
1092 order: None,
1093 parents: vec![regular_space_id],
1094 children: vec![],
1095 power_level: Some(100),
1096 },
1097 MockSpaceRoomParameters {
1098 room_id: regular_space_id,
1099 order: None,
1100 parents: vec![],
1101 children: vec![admin_subspace_id],
1102 power_level: Some(0),
1103 },
1104 MockSpaceRoomParameters {
1105 room_id: regular_subspace_id,
1106 order: None,
1107 parents: vec![admin_space_id],
1108 children: vec![],
1109 power_level: Some(0),
1110 },
1111 ],
1112 &client,
1113 &server,
1114 &factory,
1115 user_id,
1116 )
1117 .await;
1118
1119 let space_service = SpaceService::new(client.clone()).await;
1120
1121 let editable_spaces = space_service.editable_spaces().await;
1123
1124 assert_eq!(
1126 editable_spaces.iter().map(|room| room.room_id.to_owned()).collect::<Vec<_>>(),
1127 vec![admin_space_id.to_owned(), admin_subspace_id.to_owned()]
1128 );
1129 }
1130
1131 #[async_test]
1132 async fn test_joined_parents_of_child() {
1133 let server = MatrixMockServer::new().await;
1135 let client = server.client_builder().build().await;
1136 let user_id = client.user_id().unwrap();
1137 let factory = EventFactory::new();
1138
1139 server.mock_room_state_encryption().plain().mount().await;
1140
1141 let parent_space_id_1 = room_id!("!parent_space_1:example.org");
1142 let parent_space_id_2 = room_id!("!parent_space_2:example.org");
1143 let unknown_parent_space_id = room_id!("!unknown_parent_space:example.org");
1144 let child_space_id = room_id!("!child_space:example.org");
1145
1146 add_space_rooms(
1147 vec![
1148 MockSpaceRoomParameters {
1149 room_id: child_space_id,
1150 order: None,
1151 parents: vec![parent_space_id_1, parent_space_id_2, unknown_parent_space_id],
1152 children: vec![],
1153 power_level: None,
1154 },
1155 MockSpaceRoomParameters {
1156 room_id: parent_space_id_1,
1157 order: None,
1158 parents: vec![],
1159 children: vec![child_space_id],
1160 power_level: None,
1161 },
1162 MockSpaceRoomParameters {
1163 room_id: parent_space_id_2,
1164 order: None,
1165 parents: vec![],
1166 children: vec![child_space_id],
1167 power_level: None,
1168 },
1169 ],
1170 &client,
1171 &server,
1172 &factory,
1173 user_id,
1174 )
1175 .await;
1176
1177 let space_service = SpaceService::new(client.clone()).await;
1178
1179 let parents = space_service.joined_parents_of_child(child_space_id).await;
1181
1182 assert_eq!(
1184 parents.iter().map(|space| space.room_id.to_owned()).collect::<Vec<_>>(),
1185 vec![parent_space_id_1, parent_space_id_2]
1186 );
1187 }
1188
1189 #[async_test]
1190 async fn test_get_space_room_for_id() {
1191 let server = MatrixMockServer::new().await;
1192 let client = server.client_builder().build().await;
1193 let user_id = client.user_id().unwrap();
1194 let factory = EventFactory::new();
1195
1196 server.mock_room_state_encryption().plain().mount().await;
1197
1198 let space_id = room_id!("!single_space:example.org");
1199
1200 add_space_rooms(
1201 vec![MockSpaceRoomParameters {
1202 room_id: space_id,
1203 order: None,
1204 parents: vec![],
1205 children: vec![],
1206 power_level: None,
1207 }],
1208 &client,
1209 &server,
1210 &factory,
1211 user_id,
1212 )
1213 .await;
1214
1215 let space_service = SpaceService::new(client.clone()).await;
1216
1217 let found = space_service.get_space_room(space_id).await;
1218 assert!(found.is_some());
1219
1220 let expected = SpaceRoom::new_from_known(&client.get_room(space_id).unwrap(), 0);
1221 assert_eq!(found.unwrap(), expected);
1222 }
1223
1224 #[async_test]
1225 async fn test_add_child_to_space() {
1226 let server = MatrixMockServer::new().await;
1228 let client = server.client_builder().build().await;
1229 let user_id = client.user_id().unwrap();
1230 let factory = EventFactory::new();
1231
1232 server.mock_room_state_encryption().plain().mount().await;
1233
1234 let space_child_event_id = event_id!("$1");
1235 let space_parent_event_id = event_id!("$2");
1236 server.mock_set_space_child().ok(space_child_event_id.to_owned()).expect(1).mount().await;
1237 server.mock_set_space_parent().ok(space_parent_event_id.to_owned()).expect(1).mount().await;
1238
1239 let space_id = room_id!("!my_space:example.org");
1240 let child_id = room_id!("!my_child:example.org");
1241
1242 add_space_rooms(
1243 vec![
1244 MockSpaceRoomParameters {
1245 room_id: space_id,
1246 order: None,
1247 parents: vec![],
1248 children: vec![],
1249 power_level: Some(100),
1250 },
1251 MockSpaceRoomParameters {
1252 room_id: child_id,
1253 order: None,
1254 parents: vec![],
1255 children: vec![],
1256 power_level: Some(100),
1257 },
1258 ],
1259 &client,
1260 &server,
1261 &factory,
1262 user_id,
1263 )
1264 .await;
1265
1266 let space_service = SpaceService::new(client.clone()).await;
1267
1268 let result =
1270 space_service.add_child_to_space(child_id.to_owned(), space_id.to_owned()).await;
1271
1272 assert!(result.is_ok());
1274 }
1275
1276 #[async_test]
1277 async fn test_add_child_to_space_without_space_admin() {
1278 let server = MatrixMockServer::new().await;
1280 let client = server.client_builder().build().await;
1281 let user_id = client.user_id().unwrap();
1282 let factory = EventFactory::new();
1283
1284 server.mock_room_state_encryption().plain().mount().await;
1285
1286 server.mock_set_space_child().unauthorized().expect(1).mount().await;
1287 server.mock_set_space_parent().unauthorized().expect(0).mount().await;
1288
1289 let space_id = room_id!("!my_space:example.org");
1290 let child_id = room_id!("!my_child:example.org");
1291
1292 add_space_rooms(
1293 vec![
1294 MockSpaceRoomParameters {
1295 room_id: space_id,
1296 order: None,
1297 parents: vec![],
1298 children: vec![],
1299 power_level: Some(0),
1300 },
1301 MockSpaceRoomParameters {
1302 room_id: child_id,
1303 order: None,
1304 parents: vec![],
1305 children: vec![],
1306 power_level: Some(0),
1307 },
1308 ],
1309 &client,
1310 &server,
1311 &factory,
1312 user_id,
1313 )
1314 .await;
1315
1316 let space_service = SpaceService::new(client.clone()).await;
1317
1318 let result =
1320 space_service.add_child_to_space(child_id.to_owned(), space_id.to_owned()).await;
1321
1322 assert!(result.is_err());
1325 }
1326
1327 #[async_test]
1328 async fn test_add_child_to_space_without_child_admin() {
1329 let server = MatrixMockServer::new().await;
1332 let client = server.client_builder().build().await;
1333 let user_id = client.user_id().unwrap();
1334 let factory = EventFactory::new();
1335
1336 server.mock_room_state_encryption().plain().mount().await;
1337
1338 let space_child_event_id = event_id!("$1");
1339 server.mock_set_space_child().ok(space_child_event_id.to_owned()).expect(1).mount().await;
1340 server.mock_set_space_parent().unauthorized().expect(0).mount().await;
1341
1342 let space_id = room_id!("!my_space:example.org");
1343 let child_id = room_id!("!my_child:example.org");
1344
1345 add_space_rooms(
1346 vec![
1347 MockSpaceRoomParameters {
1348 room_id: space_id,
1349 order: None,
1350 parents: vec![],
1351 children: vec![],
1352 power_level: Some(100),
1353 },
1354 MockSpaceRoomParameters {
1355 room_id: child_id,
1356 order: None,
1357 parents: vec![],
1358 children: vec![],
1359 power_level: Some(0),
1360 },
1361 ],
1362 &client,
1363 &server,
1364 &factory,
1365 user_id,
1366 )
1367 .await;
1368
1369 let space_service = SpaceService::new(client.clone()).await;
1370
1371 let result =
1373 space_service.add_child_to_space(child_id.to_owned(), space_id.to_owned()).await;
1374
1375 error!("result: {:?}", result);
1376 assert!(result.is_ok());
1379 }
1380
1381 #[async_test]
1382 async fn test_remove_child_from_space() {
1383 let server = MatrixMockServer::new().await;
1385 let client = server.client_builder().build().await;
1386 let user_id = client.user_id().unwrap();
1387 let factory = EventFactory::new();
1388
1389 server.mock_room_state_encryption().plain().mount().await;
1390
1391 let space_child_event_id = event_id!("$1");
1392 let space_parent_event_id = event_id!("$2");
1393 server.mock_set_space_child().ok(space_child_event_id.to_owned()).expect(1).mount().await;
1394 server.mock_set_space_parent().ok(space_parent_event_id.to_owned()).expect(1).mount().await;
1395
1396 let parent_id = room_id!("!parent_space:example.org");
1397 let child_id = room_id!("!child_space:example.org");
1398
1399 add_space_rooms(
1400 vec![
1401 MockSpaceRoomParameters {
1402 room_id: parent_id,
1403 order: None,
1404 parents: vec![],
1405 children: vec![child_id],
1406 power_level: None,
1407 },
1408 MockSpaceRoomParameters {
1409 room_id: child_id,
1410 order: None,
1411 parents: vec![parent_id],
1412 children: vec![],
1413 power_level: None,
1414 },
1415 ],
1416 &client,
1417 &server,
1418 &factory,
1419 user_id,
1420 )
1421 .await;
1422
1423 let space_service = SpaceService::new(client.clone()).await;
1424
1425 let result =
1427 space_service.remove_child_from_space(child_id.to_owned(), parent_id.to_owned()).await;
1428
1429 assert!(result.is_ok());
1431 }
1432
1433 #[async_test]
1434 async fn test_remove_child_from_space_without_parent_event() {
1435 let server = MatrixMockServer::new().await;
1437 let client = server.client_builder().build().await;
1438 let user_id = client.user_id().unwrap();
1439 let factory = EventFactory::new();
1440
1441 server.mock_room_state_encryption().plain().mount().await;
1442
1443 let space_child_event_id = event_id!("$1");
1444 server.mock_set_space_child().ok(space_child_event_id.to_owned()).expect(1).mount().await;
1445 server.mock_set_space_parent().unauthorized().expect(0).mount().await;
1446
1447 let parent_id = room_id!("!parent_space:example.org");
1448 let child_id = room_id!("!child_space:example.org");
1449
1450 add_space_rooms(
1451 vec![
1452 MockSpaceRoomParameters {
1453 room_id: parent_id,
1454 order: None,
1455 parents: vec![],
1456 children: vec![child_id],
1457 power_level: None,
1458 },
1459 MockSpaceRoomParameters {
1460 room_id: child_id,
1461 order: None,
1462 parents: vec![],
1463 children: vec![],
1464 power_level: None,
1465 },
1466 ],
1467 &client,
1468 &server,
1469 &factory,
1470 user_id,
1471 )
1472 .await;
1473
1474 let space_service = SpaceService::new(client.clone()).await;
1475
1476 let result =
1478 space_service.remove_child_from_space(child_id.to_owned(), parent_id.to_owned()).await;
1479
1480 assert!(result.is_ok());
1483 }
1484
1485 #[async_test]
1486 async fn test_remove_child_from_space_without_child_event() {
1487 let server = MatrixMockServer::new().await;
1489 let client = server.client_builder().build().await;
1490 let user_id = client.user_id().unwrap();
1491 let factory = EventFactory::new();
1492
1493 server.mock_room_state_encryption().plain().mount().await;
1494
1495 let space_parent_event_id = event_id!("$2");
1496 server.mock_set_space_child().unauthorized().expect(0).mount().await;
1497 server.mock_set_space_parent().ok(space_parent_event_id.to_owned()).expect(1).mount().await;
1498
1499 let parent_id = room_id!("!parent_space:example.org");
1500 let child_id = room_id!("!child_space:example.org");
1501
1502 add_space_rooms(
1503 vec![
1504 MockSpaceRoomParameters {
1505 room_id: parent_id,
1506 order: None,
1507 parents: vec![],
1508 children: vec![],
1509 power_level: None,
1510 },
1511 MockSpaceRoomParameters {
1512 room_id: child_id,
1513 order: None,
1514 parents: vec![parent_id],
1515 children: vec![],
1516 power_level: None,
1517 },
1518 ],
1519 &client,
1520 &server,
1521 &factory,
1522 user_id,
1523 )
1524 .await;
1525
1526 let space_service = SpaceService::new(client.clone()).await;
1527
1528 let result =
1530 space_service.remove_child_from_space(child_id.to_owned(), parent_id.to_owned()).await;
1531
1532 assert!(result.is_ok());
1535 }
1536
1537 #[async_test]
1538 async fn test_remove_unknown_child_from_space() {
1539 let server = MatrixMockServer::new().await;
1541 let client = server.client_builder().build().await;
1542 let user_id = client.user_id().unwrap();
1543 let factory = EventFactory::new();
1544
1545 server.mock_room_state_encryption().plain().mount().await;
1546
1547 let space_child_event_id = event_id!("$1");
1548 server.mock_set_space_child().ok(space_child_event_id.to_owned()).expect(1).mount().await;
1549 server.mock_set_space_parent().unauthorized().expect(0).mount().await;
1551
1552 let parent_id = room_id!("!parent_space:example.org");
1553 let unknown_child_id = room_id!("!unknown_child:example.org");
1554
1555 add_space_rooms(
1557 vec![MockSpaceRoomParameters {
1558 room_id: parent_id,
1559 order: None,
1560 parents: vec![],
1561 children: vec![unknown_child_id],
1562 power_level: None,
1563 }],
1564 &client,
1565 &server,
1566 &factory,
1567 user_id,
1568 )
1569 .await;
1570
1571 assert!(client.get_room(unknown_child_id).is_none());
1573
1574 let space_service = SpaceService::new(client.clone()).await;
1575
1576 let result = space_service
1578 .remove_child_from_space(unknown_child_id.to_owned(), parent_id.to_owned())
1579 .await;
1580
1581 assert!(result.is_ok());
1584 }
1585
1586 #[async_test]
1587 async fn test_space_child_updates() {
1588 let server = MatrixMockServer::new().await;
1590 let client = server.client_builder().build().await;
1591 let user_id = client.user_id().unwrap();
1592 let factory = EventFactory::new();
1593
1594 server.mock_room_state_encryption().plain().mount().await;
1595
1596 let space_id = room_id!("!space:localhost");
1597 let first_child_id = room_id!("!first_child:localhost");
1598 let second_child_id = room_id!("!second_child:localhost");
1599
1600 server
1602 .sync_room(
1603 &client,
1604 JoinedRoomBuilder::new(space_id)
1605 .add_state_event(factory.create(user_id, RoomVersionId::V11).with_space_type()),
1606 )
1607 .await;
1608
1609 let space_service = SpaceService::new(client.clone()).await;
1612
1613 let (initial_values, joined_spaces_subscriber) =
1614 space_service.subscribe_to_top_level_joined_spaces().await;
1615 pin_mut!(joined_spaces_subscriber);
1616 assert_pending!(joined_spaces_subscriber);
1617
1618 assert_eq!(
1619 initial_values,
1620 vec![SpaceRoom::new_from_known(&client.get_room(space_id).unwrap(), 0)].into()
1621 );
1622
1623 assert_eq!(
1624 space_service.top_level_joined_spaces().await,
1625 vec![SpaceRoom::new_from_known(&client.get_room(space_id).unwrap(), 0)]
1626 );
1627
1628 server
1630 .sync_room(
1631 &client,
1632 JoinedRoomBuilder::new(space_id)
1633 .add_state_event(
1634 factory
1635 .space_child(space_id.to_owned(), first_child_id.to_owned())
1636 .sender(user_id),
1637 )
1638 .add_state_event(
1639 factory
1640 .space_child(space_id.to_owned(), second_child_id.to_owned())
1641 .sender(user_id),
1642 ),
1643 )
1644 .await;
1645
1646 assert_eq!(
1648 space_service.top_level_joined_spaces().await,
1649 vec![SpaceRoom::new_from_known(&client.get_room(space_id).unwrap(), 2)]
1650 );
1651 assert_next_eq!(
1652 joined_spaces_subscriber,
1653 vec![
1654 VectorDiff::Clear,
1655 VectorDiff::Append {
1656 values: vec![SpaceRoom::new_from_known(&client.get_room(space_id).unwrap(), 2)]
1657 .into()
1658 },
1659 ]
1660 );
1661
1662 server
1664 .sync_room(
1665 &client,
1666 JoinedRoomBuilder::new(space_id).add_state_bulk([Raw::new(&json!({
1667 "content": {},
1668 "type": "m.space.child",
1669 "event_id": "$cancelsecondchild",
1670 "origin_server_ts": MilliSecondsSinceUnixEpoch::now(),
1671 "sender": user_id,
1672 "state_key": second_child_id,
1673 }))
1674 .unwrap()
1675 .cast_unchecked()]),
1676 )
1677 .await;
1678
1679 assert_eq!(
1681 space_service.top_level_joined_spaces().await,
1682 vec![SpaceRoom::new_from_known(&client.get_room(space_id).unwrap(), 1)]
1683 );
1684 assert_next_eq!(
1685 joined_spaces_subscriber,
1686 vec![
1687 VectorDiff::Clear,
1688 VectorDiff::Append {
1689 values: vec![SpaceRoom::new_from_known(&client.get_room(space_id).unwrap(), 1)]
1690 .into()
1691 },
1692 ]
1693 );
1694 }
1695
1696 async fn add_space_rooms(
1697 rooms: Vec<MockSpaceRoomParameters>,
1698 client: &Client,
1699 server: &MatrixMockServer,
1700 factory: &EventFactory,
1701 user_id: &UserId,
1702 ) {
1703 for parameters in rooms {
1704 let mut builder = JoinedRoomBuilder::new(parameters.room_id)
1705 .add_state_event(factory.create(user_id, RoomVersionId::V1).with_space_type());
1706
1707 if let Some(order) = parameters.order {
1708 builder = builder.add_account_data(factory.space_order(order));
1709 }
1710
1711 for parent_id in parameters.parents {
1712 builder = builder.add_state_event(
1713 factory
1714 .space_parent(parent_id.to_owned(), parameters.room_id.to_owned())
1715 .sender(user_id),
1716 );
1717 }
1718
1719 for child_id in parameters.children {
1720 builder = builder.add_state_event(
1721 factory
1722 .space_child(parameters.room_id.to_owned(), child_id.to_owned())
1723 .sender(user_id),
1724 );
1725 }
1726
1727 let mut power_levels = if let Some(power_level) = parameters.power_level {
1728 BTreeMap::from([(user_id.to_owned(), power_level.into())])
1729 } else {
1730 BTreeMap::from([(user_id.to_owned(), 100.into())])
1731 };
1732
1733 builder = builder.add_state_event(
1734 factory.power_levels(&mut power_levels).state_key("").sender(user_id),
1735 );
1736
1737 server.sync_room(client, builder).await;
1738 }
1739 }
1740
1741 struct MockSpaceRoomParameters {
1742 room_id: &'static RoomId,
1743 order: Option<&'static str>,
1744 parents: Vec<&'static RoomId>,
1745 children: Vec<&'static RoomId>,
1746 power_level: Option<i32>,
1747 }
1748}