tuwunel_service/rooms/spaces/
local.rs1use futures::{FutureExt, StreamExt, TryFutureExt};
2use ruma::{
3 RoomId, api::federation::space::SpaceHierarchyParentSummary as ParentSummary,
4 events::space::child::HierarchySpaceChildEvent, room::RoomSummary, serde::Raw,
5};
6use tuwunel_core::{
7 Err, Error, Event, Result, debug, error, implement,
8 utils::{future::TryExtExt, timepoint_has_passed},
9};
10
11use super::{Accessibility, Cached, Identifier};
12
13#[implement(super::Service)]
15#[tracing::instrument(name = "local", level = "debug", skip_all)]
16pub(super) async fn get_summary_and_children_local(
17 &self,
18 current_room: &RoomId,
19 sender: &Identifier<'_>,
20) -> Result<Accessibility> {
21 use Accessibility::{Accessible, Inaccessible};
22
23 match self.cache_get(current_room).await {
24 | Err(e) if !e.is_not_found() => {
25 error!(?current_room, "cache error: {e}");
26 return Err(e);
27 },
28 | Ok(Cached { expires, summary: Some(cached) }) if !timepoint_has_passed(expires) => {
29 debug!(?current_room, ?expires, "cache hit");
30 return self
31 .is_accessible_child(current_room, &cached.summary.join_rule, sender)
32 .await
33 .then(|| Ok(Accessible(cached)))
34 .unwrap_or(Ok(Inaccessible));
35 },
36 | Ok(Cached { expires, summary: None }) if !timepoint_has_passed(expires) => {
37 debug!(?current_room, ?expires, "negative cache hit");
39 },
40 | _ => {
41 debug!(?current_room, "no usable cache entry");
43 },
44 }
45
46 if !self
47 .services
48 .state_cache
49 .server_in_room(self.services.server.name.as_ref(), current_room)
50 .await
51 {
52 debug!(?current_room, "no local membership; defer to federation");
53 return Err!(Request(NotFound("Space room not found locally.")));
54 }
55
56 let children_state: Vec<_> = self
57 .get_space_child_events(current_room)
58 .map(Event::into_format)
59 .collect()
60 .await;
61
62 let summary = self
63 .get_room_summary(current_room, children_state, sender)
64 .boxed()
65 .await;
66
67 match summary {
68 | Ok(Inaccessible) => self.cache_put(current_room, None),
69 | Ok(Accessible(ref summary)) => self.cache_put(current_room, Some(summary)),
70 | _ => (),
71 }
72
73 summary
74}
75
76#[implement(super::Service)]
77pub(super) async fn get_room_summary(
78 &self,
79 room_id: &RoomId,
80 children_state: Vec<Raw<HierarchySpaceChildEvent>>,
81 sender: &Identifier<'_>,
82) -> Result<Accessibility, Error> {
83 let join_rule = self
84 .services
85 .state_accessor
86 .get_join_rules(room_id)
87 .await;
88
89 let is_accessible_child = self
90 .is_accessible_child(room_id, &join_rule.clone().into(), sender)
91 .await;
92
93 if !is_accessible_child {
94 return Ok(Accessibility::Inaccessible);
95 }
96
97 let name = self
98 .services
99 .state_accessor
100 .get_name(room_id)
101 .ok();
102
103 let topic = self
104 .services
105 .state_accessor
106 .get_room_topic(room_id)
107 .ok();
108
109 let room_type = self
110 .services
111 .state_accessor
112 .get_room_type(room_id)
113 .ok();
114
115 let world_readable = self
116 .services
117 .state_accessor
118 .is_world_readable(room_id);
119
120 let guest_can_join = self
121 .services
122 .state_accessor
123 .guest_can_join(room_id);
124
125 let num_joined_members = self
126 .services
127 .state_cache
128 .room_joined_count(room_id)
129 .unwrap_or(0);
130
131 let canonical_alias = self
132 .services
133 .state_accessor
134 .get_canonical_alias(room_id)
135 .ok();
136
137 let avatar_url = self
138 .services
139 .state_accessor
140 .get_avatar(room_id)
141 .map_ok(|content| content.url)
142 .ok();
143
144 let room_version = self.services.state.get_room_version(room_id).ok();
145
146 let encryption = self
147 .services
148 .state_accessor
149 .get_room_encryption(room_id)
150 .ok();
151
152 let (
153 canonical_alias,
154 name,
155 num_joined_members,
156 topic,
157 world_readable,
158 guest_can_join,
159 avatar_url,
160 room_type,
161 room_version,
162 encryption,
163 ) = futures::join!(
164 canonical_alias,
165 name,
166 num_joined_members,
167 topic,
168 world_readable,
169 guest_can_join,
170 avatar_url,
171 room_type,
172 room_version,
173 encryption,
174 );
175
176 let summary = ParentSummary {
177 children_state,
178 summary: RoomSummary {
179 avatar_url: avatar_url.flatten(),
180 canonical_alias,
181 name,
182 topic,
183 world_readable,
184 guest_can_join,
185 room_type,
186 encryption,
187 room_version,
188 room_id: room_id.to_owned(),
189 num_joined_members: num_joined_members.try_into().unwrap_or_default(),
190 join_rule: join_rule.clone().into(),
191 },
192 };
193
194 Ok(Accessibility::Accessible(summary))
195}