fractal/session/model/room/
aliases.rsuse gtk::{glib, glib::closure_local, prelude::*, subclass::prelude::*};
use matrix_sdk::{deserialized_responses::RawSyncOrStrippedState, reqwest::StatusCode};
use ruma::{
api::client::{
alias::{create_alias, delete_alias},
room,
},
events::{room::canonical_alias::RoomCanonicalAliasEventContent, SyncStateEvent},
OwnedRoomAliasId,
};
use tracing::error;
use super::Room;
use crate::spawn_tokio;
mod imp {
use std::{cell::RefCell, marker::PhantomData, sync::LazyLock};
use glib::subclass::Signal;
use super::*;
#[derive(Debug, Default, glib::Properties)]
#[properties(wrapper_type = super::RoomAliases)]
pub struct RoomAliases {
#[property(get)]
room: glib::WeakRef<Room>,
pub(super) canonical_alias: RefCell<Option<OwnedRoomAliasId>>,
#[property(get = Self::canonical_alias_string)]
canonical_alias_string: PhantomData<Option<String>>,
pub(super) alt_aliases: RefCell<Vec<OwnedRoomAliasId>>,
#[property(get)]
alt_aliases_model: gtk::StringList,
#[property(get = Self::alias_string)]
alias_string: PhantomData<Option<String>>,
}
#[glib::object_subclass]
impl ObjectSubclass for RoomAliases {
const NAME: &'static str = "RoomAliases";
type Type = super::RoomAliases;
}
#[glib::derived_properties]
impl ObjectImpl for RoomAliases {
fn signals() -> &'static [Signal] {
static SIGNALS: LazyLock<Vec<Signal>> =
LazyLock::new(|| vec![Signal::builder("changed").build()]);
SIGNALS.as_ref()
}
}
impl RoomAliases {
pub(super) fn set_room(&self, room: &Room) {
self.room.set(Some(room));
}
fn set_canonical_alias(&self, canonical_alias: Option<OwnedRoomAliasId>) -> bool {
if *self.canonical_alias.borrow() == canonical_alias {
return false;
}
self.canonical_alias.replace(canonical_alias);
let obj = self.obj();
obj.notify_canonical_alias_string();
obj.notify_alias_string();
true
}
fn canonical_alias_string(&self) -> Option<String> {
self.canonical_alias
.borrow()
.as_ref()
.map(ToString::to_string)
}
fn set_alt_aliases(&self, alt_aliases: Vec<OwnedRoomAliasId>) -> bool {
if *self.alt_aliases.borrow() == alt_aliases {
return false;
}
let (pos, removed) = {
let old_aliases = &*self.alt_aliases.borrow();
let mut pos = None;
for (i, old_alias) in old_aliases.iter().enumerate() {
if !alt_aliases.get(i).is_some_and(|alias| alias == old_alias) {
pos = Some(i);
break;
}
}
let old_len = old_aliases.len();
if pos.is_none() {
let new_len = alt_aliases.len();
if old_len < new_len {
pos = Some(old_len);
}
}
let Some(pos) = pos else {
return false;
};
let removed = old_len.saturating_sub(pos);
(pos, removed)
};
let additions = alt_aliases.get(pos..).unwrap_or_default().to_owned();
let additions_str = additions
.iter()
.map(|alias| alias.as_str())
.collect::<Vec<_>>();
let Ok(pos) = u32::try_from(pos) else {
return false;
};
let Ok(removed) = u32::try_from(removed) else {
return false;
};
self.alt_aliases.replace(alt_aliases);
self.alt_aliases_model.splice(pos, removed, &additions_str);
self.obj().notify_alias_string();
true
}
fn alias_string(&self) -> Option<String> {
self.canonical_alias_string()
.or_else(|| self.alt_aliases_model.string(0).map(Into::into))
}
pub(super) fn update(&self) {
let Some(room) = self.room.upgrade() else {
return;
};
let obj = self.obj();
let _guard = obj.freeze_notify();
let matrix_room = room.matrix_room();
let mut changed = self.set_canonical_alias(matrix_room.canonical_alias());
changed |= self.set_alt_aliases(matrix_room.alt_aliases());
if changed {
obj.emit_by_name::<()>("changed", &[]);
}
}
}
}
glib::wrapper! {
pub struct RoomAliases(ObjectSubclass<imp::RoomAliases>);
}
impl RoomAliases {
pub fn new() -> Self {
glib::Object::new()
}
pub(crate) fn init(&self, room: &Room) {
self.imp().set_room(room);
}
pub(crate) fn update(&self) {
self.imp().update();
}
async fn canonical_alias_event_content(
&self,
) -> Result<Option<RoomCanonicalAliasEventContent>, ()> {
let Some(room) = self.room() else {
return Err(());
};
let matrix_room = room.matrix_room().clone();
let handle = spawn_tokio!(async move {
matrix_room
.get_state_event_static::<RoomCanonicalAliasEventContent>()
.await
});
let raw_event = match handle.await.unwrap() {
Ok(Some(RawSyncOrStrippedState::Sync(raw_event))) => raw_event,
Ok(_) => return Ok(None),
Err(error) => {
error!("Could not get canonical alias event: {error}");
return Err(());
}
};
match raw_event.deserialize() {
Ok(SyncStateEvent::Original(event)) => Ok(Some(event.content)),
Ok(_) => Ok(None),
Err(error) => {
error!("Could not deserialize canonical alias event: {error}");
Err(())
}
}
}
pub(crate) fn canonical_alias(&self) -> Option<OwnedRoomAliasId> {
self.imp().canonical_alias.borrow().clone()
}
pub(crate) async fn remove_canonical_alias(&self, alias: &OwnedRoomAliasId) -> Result<(), ()> {
let mut event_content = self
.canonical_alias_event_content()
.await?
.unwrap_or_default();
if !event_content.alias.take().is_some_and(|a| a == *alias) {
return Err(());
}
let Some(room) = self.room() else {
return Err(());
};
let matrix_room = room.matrix_room().clone();
let handle = spawn_tokio!(async move { matrix_room.send_state_event(event_content).await });
match handle.await.unwrap() {
Ok(_) => Ok(()),
Err(error) => {
error!("Could not remove canonical alias: {error}");
Err(())
}
}
}
pub(crate) async fn set_canonical_alias(&self, alias: OwnedRoomAliasId) -> Result<(), ()> {
let mut event_content = self
.canonical_alias_event_content()
.await?
.unwrap_or_default();
if event_content.alias.as_ref().is_some_and(|a| *a == alias) {
return Err(());
}
let Some(room) = self.room() else {
return Err(());
};
let alt_alias_pos = event_content.alt_aliases.iter().position(|a| *a == alias);
if let Some(pos) = alt_alias_pos {
event_content.alt_aliases.remove(pos);
}
if let Some(old_canonical) = event_content.alias.replace(alias) {
let has_old_canonical = event_content.alt_aliases.contains(&old_canonical);
if !has_old_canonical {
event_content.alt_aliases.push(old_canonical);
}
}
let matrix_room = room.matrix_room().clone();
let handle = spawn_tokio!(async move { matrix_room.send_state_event(event_content).await });
match handle.await.unwrap() {
Ok(_) => Ok(()),
Err(error) => {
error!("Could not set canonical alias: {error}");
Err(())
}
}
}
pub(crate) fn alt_aliases(&self) -> Vec<OwnedRoomAliasId> {
self.imp().alt_aliases.borrow().clone()
}
pub(crate) async fn remove_alt_alias(&self, alias: &OwnedRoomAliasId) -> Result<(), ()> {
let mut event_content = self
.canonical_alias_event_content()
.await?
.unwrap_or_default();
let alt_alias_pos = event_content.alt_aliases.iter().position(|a| a == alias);
if let Some(pos) = alt_alias_pos {
event_content.alt_aliases.remove(pos);
} else {
return Err(());
}
let Some(room) = self.room() else {
return Err(());
};
let matrix_room = room.matrix_room().clone();
let handle = spawn_tokio!(async move { matrix_room.send_state_event(event_content).await });
match handle.await.unwrap() {
Ok(_) => Ok(()),
Err(error) => {
error!("Could not remove alt alias: {error}");
Err(())
}
}
}
pub(crate) async fn add_alt_alias(
&self,
alias: OwnedRoomAliasId,
) -> Result<(), AddAltAliasError> {
let Ok(event_content) = self.canonical_alias_event_content().await else {
return Err(AddAltAliasError::Other);
};
let mut event_content = event_content.unwrap_or_default();
if event_content.alias.as_ref().is_some_and(|a| *a == alias)
|| event_content.alt_aliases.contains(&alias)
{
error!("Cannot add alias already listed");
return Err(AddAltAliasError::Other);
}
let Some(room) = self.room() else {
return Err(AddAltAliasError::Other);
};
let matrix_room = room.matrix_room().clone();
let client = matrix_room.client();
let alias_clone = alias.clone();
let handle = spawn_tokio!(async move { client.resolve_room_alias(&alias_clone).await });
match handle.await.unwrap() {
Ok(response) => {
if response.room_id != matrix_room.room_id() {
error!("Cannot add alias that points to other room");
return Err(AddAltAliasError::InvalidRoomId);
}
}
Err(error) => {
error!("Could not check room alias: {error}");
if error
.as_client_api_error()
.is_some_and(|e| e.status_code == StatusCode::NOT_FOUND)
{
return Err(AddAltAliasError::NotRegistered);
}
return Err(AddAltAliasError::Other);
}
}
event_content.alt_aliases.push(alias);
let handle = spawn_tokio!(async move { matrix_room.send_state_event(event_content).await });
match handle.await.unwrap() {
Ok(_) => Ok(()),
Err(error) => {
error!("Could not add alt alias: {error}");
Err(AddAltAliasError::Other)
}
}
}
pub(crate) fn alias(&self) -> Option<OwnedRoomAliasId> {
self.canonical_alias()
.or_else(|| self.imp().alt_aliases.borrow().first().cloned())
}
pub(crate) async fn local_aliases(&self) -> Result<Vec<OwnedRoomAliasId>, ()> {
let Some(room) = self.room() else {
return Err(());
};
let matrix_room = room.matrix_room();
let client = matrix_room.client();
let room_id = matrix_room.room_id().to_owned();
let handle = spawn_tokio!(async move {
client
.send(room::aliases::v3::Request::new(room_id), None)
.await
});
match handle.await.unwrap() {
Ok(response) => Ok(response.aliases),
Err(error) => {
error!("Could not fetch local room aliases: {error}");
Err(())
}
}
}
pub(crate) async fn unregister_local_alias(&self, alias: OwnedRoomAliasId) -> Result<(), ()> {
let Some(room) = self.room() else {
return Err(());
};
let matrix_room = room.matrix_room();
let client = matrix_room.client();
let request = delete_alias::v3::Request::new(alias);
let handle = spawn_tokio!(async move { client.send(request, None).await });
match handle.await.unwrap() {
Ok(_) => Ok(()),
Err(error) => {
error!("Could not unregister local alias: {error}");
Err(())
}
}
}
pub(crate) async fn register_local_alias(
&self,
alias: OwnedRoomAliasId,
) -> Result<(), RegisterLocalAliasError> {
let Some(room) = self.room() else {
return Err(RegisterLocalAliasError::Other);
};
let matrix_room = room.matrix_room();
let client = matrix_room.client();
let room_id = matrix_room.room_id().to_owned();
let request = create_alias::v3::Request::new(alias, room_id);
let handle = spawn_tokio!(async move { client.send(request, None).await });
match handle.await.unwrap() {
Ok(_) => Ok(()),
Err(error) => {
error!("Could not register local alias: {error}");
if error
.as_client_api_error()
.is_some_and(|e| e.status_code == StatusCode::CONFLICT)
{
Err(RegisterLocalAliasError::AlreadyInUse)
} else {
Err(RegisterLocalAliasError::Other)
}
}
}
}
pub(crate) fn connect_changed<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
self.connect_closure(
"changed",
true,
closure_local!(move |obj: Self| {
f(&obj);
}),
)
}
}
impl Default for RoomAliases {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum AddAltAliasError {
NotRegistered,
InvalidRoomId,
Other,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum RegisterLocalAliasError {
AlreadyInUse,
Other,
}