fractal/session/model/room/
aliases.rs1use gtk::{glib, glib::closure_local, prelude::*, subclass::prelude::*};
2use matrix_sdk::{deserialized_responses::RawSyncOrStrippedState, reqwest::StatusCode};
3use ruma::{
4 OwnedRoomAliasId,
5 api::client::{
6 alias::{create_alias, delete_alias},
7 room,
8 },
9 events::{SyncStateEvent, room::canonical_alias::RoomCanonicalAliasEventContent},
10};
11use tracing::error;
12
13use super::Room;
14use crate::spawn_tokio;
15
16mod imp {
17 use std::{cell::RefCell, marker::PhantomData, sync::LazyLock};
18
19 use glib::subclass::Signal;
20
21 use super::*;
22
23 #[derive(Debug, Default, glib::Properties)]
24 #[properties(wrapper_type = super::RoomAliases)]
25 pub struct RoomAliases {
26 #[property(get)]
28 room: glib::WeakRef<Room>,
29 pub(super) canonical_alias: RefCell<Option<OwnedRoomAliasId>>,
31 #[property(get = Self::canonical_alias_string)]
33 canonical_alias_string: PhantomData<Option<String>>,
34 pub(super) alt_aliases: RefCell<Vec<OwnedRoomAliasId>>,
36 #[property(get)]
38 alt_aliases_model: gtk::StringList,
39 #[property(get = Self::alias_string)]
43 alias_string: PhantomData<Option<String>>,
44 }
45
46 #[glib::object_subclass]
47 impl ObjectSubclass for RoomAliases {
48 const NAME: &'static str = "RoomAliases";
49 type Type = super::RoomAliases;
50 }
51
52 #[glib::derived_properties]
53 impl ObjectImpl for RoomAliases {
54 fn signals() -> &'static [Signal] {
55 static SIGNALS: LazyLock<Vec<Signal>> =
56 LazyLock::new(|| vec![Signal::builder("changed").build()]);
57 SIGNALS.as_ref()
58 }
59 }
60
61 impl RoomAliases {
62 pub(super) fn set_room(&self, room: &Room) {
64 self.room.set(Some(room));
65 }
66
67 fn set_canonical_alias(&self, canonical_alias: Option<OwnedRoomAliasId>) -> bool {
71 if *self.canonical_alias.borrow() == canonical_alias {
72 return false;
73 }
74
75 self.canonical_alias.replace(canonical_alias);
76
77 let obj = self.obj();
78 obj.notify_canonical_alias_string();
79 obj.notify_alias_string();
80 true
81 }
82
83 fn canonical_alias_string(&self) -> Option<String> {
85 self.canonical_alias
86 .borrow()
87 .as_ref()
88 .map(ToString::to_string)
89 }
90
91 fn set_alt_aliases(&self, alt_aliases: Vec<OwnedRoomAliasId>) -> bool {
95 if *self.alt_aliases.borrow() == alt_aliases {
97 return false;
98 }
99
100 let (pos, removed) = {
101 let old_aliases = &*self.alt_aliases.borrow();
102 let mut pos = None;
103
104 for (i, old_alias) in old_aliases.iter().enumerate() {
106 if alt_aliases.get(i).is_none_or(|alias| alias != old_alias) {
107 pos = Some(i);
108 break;
109 }
110 }
111
112 let old_len = old_aliases.len();
114 if pos.is_none() {
115 let new_len = alt_aliases.len();
116
117 if old_len < new_len {
118 pos = Some(old_len);
119 }
120 }
121
122 let Some(pos) = pos else {
123 return false;
124 };
125
126 let removed = old_len.saturating_sub(pos);
127
128 (pos, removed)
129 };
130
131 let additions = alt_aliases.get(pos..).unwrap_or_default().to_owned();
132 let additions_str = additions
133 .iter()
134 .map(|alias| alias.as_str())
135 .collect::<Vec<_>>();
136
137 let Ok(pos) = u32::try_from(pos) else {
138 return false;
139 };
140 let Ok(removed) = u32::try_from(removed) else {
141 return false;
142 };
143
144 self.alt_aliases.replace(alt_aliases);
145 self.alt_aliases_model.splice(pos, removed, &additions_str);
146
147 self.obj().notify_alias_string();
148 true
149 }
150
151 fn alias_string(&self) -> Option<String> {
153 self.canonical_alias_string()
154 .or_else(|| self.alt_aliases_model.string(0).map(Into::into))
155 }
156
157 pub(super) fn update(&self) {
159 let Some(room) = self.room.upgrade() else {
160 return;
161 };
162
163 let obj = self.obj();
164 let _guard = obj.freeze_notify();
165
166 let matrix_room = room.matrix_room();
167 let mut changed = self.set_canonical_alias(matrix_room.canonical_alias());
168 changed |= self.set_alt_aliases(matrix_room.alt_aliases());
169
170 if changed {
171 obj.emit_by_name::<()>("changed", &[]);
172 }
173 }
174 }
175}
176
177glib::wrapper! {
178 pub struct RoomAliases(ObjectSubclass<imp::RoomAliases>);
180}
181
182impl RoomAliases {
183 pub fn new() -> Self {
184 glib::Object::new()
185 }
186
187 pub(crate) fn init(&self, room: &Room) {
189 self.imp().set_room(room);
190 }
191
192 pub(crate) fn update(&self) {
194 self.imp().update();
195 }
196
197 async fn canonical_alias_event_content(
199 &self,
200 ) -> Result<Option<RoomCanonicalAliasEventContent>, ()> {
201 let Some(room) = self.room() else {
202 return Err(());
203 };
204
205 let matrix_room = room.matrix_room().clone();
206 let handle = spawn_tokio!(async move {
207 matrix_room
208 .get_state_event_static::<RoomCanonicalAliasEventContent>()
209 .await
210 });
211
212 let raw_event = match handle.await.unwrap() {
213 Ok(Some(RawSyncOrStrippedState::Sync(raw_event))) => raw_event,
214 Ok(_) => return Ok(None),
216 Err(error) => {
217 error!("Could not get canonical alias event: {error}");
218 return Err(());
219 }
220 };
221
222 match raw_event.deserialize() {
223 Ok(SyncStateEvent::Original(event)) => Ok(Some(event.content)),
224 Ok(_) => Ok(None),
226 Err(error) => {
227 error!("Could not deserialize canonical alias event: {error}");
228 Err(())
229 }
230 }
231 }
232
233 pub(crate) fn canonical_alias(&self) -> Option<OwnedRoomAliasId> {
235 self.imp().canonical_alias.borrow().clone()
236 }
237
238 pub(crate) async fn remove_canonical_alias(&self, alias: &OwnedRoomAliasId) -> Result<(), ()> {
242 let mut event_content = self
243 .canonical_alias_event_content()
244 .await?
245 .unwrap_or_default();
246
247 if event_content.alias.take().is_none_or(|a| a != *alias) {
249 return Err(());
251 }
252
253 let Some(room) = self.room() else {
254 return Err(());
255 };
256
257 let matrix_room = room.matrix_room().clone();
258 let handle = spawn_tokio!(async move { matrix_room.send_state_event(event_content).await });
259
260 match handle.await.unwrap() {
261 Ok(_) => Ok(()),
262 Err(error) => {
263 error!("Could not remove canonical alias: {error}");
264 Err(())
265 }
266 }
267 }
268
269 pub(crate) async fn set_canonical_alias(&self, alias: OwnedRoomAliasId) -> Result<(), ()> {
273 let mut event_content = self
274 .canonical_alias_event_content()
275 .await?
276 .unwrap_or_default();
277
278 if event_content.alias.as_ref().is_some_and(|a| *a == alias) {
279 return Err(());
281 }
282
283 let Some(room) = self.room() else {
284 return Err(());
285 };
286
287 let alt_alias_pos = event_content.alt_aliases.iter().position(|a| *a == alias);
289 if let Some(pos) = alt_alias_pos {
290 event_content.alt_aliases.remove(pos);
291 }
292
293 if let Some(old_canonical) = event_content.alias.replace(alias) {
295 let has_old_canonical = event_content.alt_aliases.contains(&old_canonical);
297
298 if !has_old_canonical {
299 event_content.alt_aliases.push(old_canonical);
300 }
301 }
302
303 let matrix_room = room.matrix_room().clone();
304 let handle = spawn_tokio!(async move { matrix_room.send_state_event(event_content).await });
305
306 match handle.await.unwrap() {
307 Ok(_) => Ok(()),
308 Err(error) => {
309 error!("Could not set canonical alias: {error}");
310 Err(())
311 }
312 }
313 }
314
315 pub(crate) fn alt_aliases(&self) -> Vec<OwnedRoomAliasId> {
317 self.imp().alt_aliases.borrow().clone()
318 }
319
320 pub(crate) async fn remove_alt_alias(&self, alias: &OwnedRoomAliasId) -> Result<(), ()> {
324 let mut event_content = self
325 .canonical_alias_event_content()
326 .await?
327 .unwrap_or_default();
328
329 let alt_alias_pos = event_content.alt_aliases.iter().position(|a| a == alias);
331 if let Some(pos) = alt_alias_pos {
332 event_content.alt_aliases.remove(pos);
333 } else {
334 return Err(());
336 }
337
338 let Some(room) = self.room() else {
339 return Err(());
340 };
341
342 let matrix_room = room.matrix_room().clone();
343 let handle = spawn_tokio!(async move { matrix_room.send_state_event(event_content).await });
344
345 match handle.await.unwrap() {
346 Ok(_) => Ok(()),
347 Err(error) => {
348 error!("Could not remove alt alias: {error}");
349 Err(())
350 }
351 }
352 }
353
354 pub(crate) async fn add_alt_alias(
358 &self,
359 alias: OwnedRoomAliasId,
360 ) -> Result<(), AddAltAliasError> {
361 let Ok(event_content) = self.canonical_alias_event_content().await else {
362 return Err(AddAltAliasError::Other);
363 };
364
365 let mut event_content = event_content.unwrap_or_default();
366
367 if event_content.alias.as_ref().is_some_and(|a| *a == alias)
369 || event_content.alt_aliases.contains(&alias)
370 {
371 error!("Cannot add alias already listed");
372 return Err(AddAltAliasError::Other);
373 }
374
375 let Some(room) = self.room() else {
376 return Err(AddAltAliasError::Other);
377 };
378
379 let matrix_room = room.matrix_room().clone();
380
381 let client = matrix_room.client();
383 let alias_clone = alias.clone();
384 let handle = spawn_tokio!(async move { client.resolve_room_alias(&alias_clone).await });
385
386 match handle.await.unwrap() {
387 Ok(response) => {
388 if response.room_id != matrix_room.room_id() {
389 error!("Cannot add alias that points to other room");
390 return Err(AddAltAliasError::InvalidRoomId);
391 }
392 }
393 Err(error) => {
394 error!("Could not check room alias: {error}");
395 if error
396 .as_client_api_error()
397 .is_some_and(|e| e.status_code == StatusCode::NOT_FOUND)
398 {
399 return Err(AddAltAliasError::NotRegistered);
400 }
401
402 return Err(AddAltAliasError::Other);
403 }
404 }
405
406 event_content.alt_aliases.push(alias);
408 let handle = spawn_tokio!(async move { matrix_room.send_state_event(event_content).await });
409
410 match handle.await.unwrap() {
411 Ok(_) => Ok(()),
412 Err(error) => {
413 error!("Could not add alt alias: {error}");
414 Err(AddAltAliasError::Other)
415 }
416 }
417 }
418
419 pub(crate) fn alias(&self) -> Option<OwnedRoomAliasId> {
424 self.canonical_alias()
425 .or_else(|| self.imp().alt_aliases.borrow().first().cloned())
426 }
427
428 pub(crate) async fn local_aliases(&self) -> Result<Vec<OwnedRoomAliasId>, ()> {
430 let Some(room) = self.room() else {
431 return Err(());
432 };
433
434 let matrix_room = room.matrix_room();
435 let client = matrix_room.client();
436 let room_id = matrix_room.room_id().to_owned();
437
438 let handle =
439 spawn_tokio!(
440 async move { client.send(room::aliases::v3::Request::new(room_id)).await }
441 );
442
443 match handle.await.unwrap() {
444 Ok(response) => Ok(response.aliases),
445 Err(error) => {
446 error!("Could not fetch local room aliases: {error}");
447 Err(())
448 }
449 }
450 }
451
452 pub(crate) async fn unregister_local_alias(&self, alias: OwnedRoomAliasId) -> Result<(), ()> {
454 let Some(room) = self.room() else {
455 return Err(());
456 };
457
458 let matrix_room = room.matrix_room();
460 let client = matrix_room.client();
461
462 let request = delete_alias::v3::Request::new(alias);
463 let handle = spawn_tokio!(async move { client.send(request).await });
464
465 match handle.await.unwrap() {
466 Ok(_) => Ok(()),
467 Err(error) => {
468 error!("Could not unregister local alias: {error}");
469 Err(())
470 }
471 }
472 }
473
474 pub(crate) async fn register_local_alias(
476 &self,
477 alias: OwnedRoomAliasId,
478 ) -> Result<(), RegisterLocalAliasError> {
479 let Some(room) = self.room() else {
480 return Err(RegisterLocalAliasError::Other);
481 };
482
483 let matrix_room = room.matrix_room();
485 let client = matrix_room.client();
486 let room_id = matrix_room.room_id().to_owned();
487
488 let request = create_alias::v3::Request::new(alias, room_id);
489 let handle = spawn_tokio!(async move { client.send(request).await });
490
491 match handle.await.unwrap() {
492 Ok(_) => Ok(()),
493 Err(error) => {
494 error!("Could not register local alias: {error}");
495
496 if error
497 .as_client_api_error()
498 .is_some_and(|e| e.status_code == StatusCode::CONFLICT)
499 {
500 Err(RegisterLocalAliasError::AlreadyInUse)
501 } else {
502 Err(RegisterLocalAliasError::Other)
503 }
504 }
505 }
506 }
507
508 pub(crate) fn connect_changed<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
510 self.connect_closure(
511 "changed",
512 true,
513 closure_local!(move |obj: Self| {
514 f(&obj);
515 }),
516 )
517 }
518}
519
520impl Default for RoomAliases {
521 fn default() -> Self {
522 Self::new()
523 }
524}
525
526#[derive(Debug, Clone, Copy, PartialEq, Eq)]
528pub(crate) enum AddAltAliasError {
529 NotRegistered,
531 InvalidRoomId,
533 Other,
535}
536
537#[derive(Debug, Clone, Copy, PartialEq, Eq)]
539pub(crate) enum RegisterLocalAliasError {
540 AlreadyInUse,
542 Other,
544}