fractal/session/model/
remote_room.rsuse std::cell::RefCell;
use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*};
use ruma::{
api::client::space::{get_hierarchy, SpaceHierarchyRoomsChunk},
assign, uint, OwnedRoomAliasId, OwnedRoomId,
};
use tracing::{debug, warn};
use super::Session;
use crate::{
components::{AvatarImage, AvatarUriSource, PillSource},
prelude::*,
spawn, spawn_tokio,
utils::{matrix::MatrixRoomIdUri, string::linkify, LoadingState},
};
mod imp {
use std::cell::{Cell, OnceCell};
use super::*;
#[derive(Default, glib::Properties)]
#[properties(wrapper_type = super::RemoteRoom)]
pub struct RemoteRoom {
#[property(get, set = Self::set_session, construct_only)]
pub session: glib::WeakRef<Session>,
pub uri: OnceCell<MatrixRoomIdUri>,
pub room_id: RefCell<Option<OwnedRoomId>>,
pub alias: RefCell<Option<OwnedRoomAliasId>>,
#[property(get)]
pub name: RefCell<Option<String>>,
#[property(get)]
pub topic: RefCell<Option<String>>,
#[property(get)]
pub topic_linkified: RefCell<Option<String>>,
#[property(get)]
pub joined_members_count: Cell<u32>,
#[property(get, builder(LoadingState::default()))]
pub loading_state: Cell<LoadingState>,
}
#[glib::object_subclass]
impl ObjectSubclass for RemoteRoom {
const NAME: &'static str = "RemoteRoom";
type Type = super::RemoteRoom;
type ParentType = PillSource;
}
#[glib::derived_properties]
impl ObjectImpl for RemoteRoom {}
impl PillSourceImpl for RemoteRoom {
fn identifier(&self) -> String {
self.uri.get().unwrap().id.to_string()
}
}
impl RemoteRoom {
fn set_session(&self, session: &Session) {
self.session.set(Some(session));
self.obj().avatar_data().set_image(Some(AvatarImage::new(
session,
AvatarUriSource::Room,
None,
None,
)));
}
fn set_room_id(&self, room_id: Option<OwnedRoomId>) {
if *self.room_id.borrow() == room_id {
return;
}
self.room_id.replace(room_id);
}
fn set_alias(&self, alias: Option<OwnedRoomAliasId>) {
if *self.alias.borrow() == alias {
return;
}
self.alias.replace(alias);
self.update_display_name();
}
fn set_name(&self, name: Option<String>) {
if *self.name.borrow() == name {
return;
}
self.name.replace(name);
self.obj().notify_name();
self.update_display_name();
}
pub(super) fn update_display_name(&self) {
let display_name = self
.name
.borrow()
.clone()
.or_else(|| self.alias.borrow().as_ref().map(ToString::to_string))
.unwrap_or_else(|| self.identifier());
self.obj().set_display_name(display_name);
}
fn set_topic(&self, topic: Option<String>) {
let topic =
topic.filter(|s| !s.is_empty() && s.find(|c: char| !c.is_whitespace()).is_some());
if *self.topic.borrow() == topic {
return;
}
let topic_linkified = topic.as_deref().map(|t| {
let mut s = linkify(t);
s.truncate_end_whitespaces();
s
});
self.topic.replace(topic);
self.topic_linkified.replace(topic_linkified);
let obj = self.obj();
obj.notify_topic();
obj.notify_topic_linkified();
}
fn set_joined_members_count(&self, count: u32) {
if self.joined_members_count.get() == count {
return;
}
self.joined_members_count.set(count);
self.obj().notify_joined_members_count();
}
pub(super) fn set_loading_state(&self, loading_state: LoadingState) {
if self.loading_state.get() == loading_state {
return;
}
self.loading_state.set(loading_state);
self.obj().notify_loading_state();
}
pub(super) fn update_data(&self, data: SpaceHierarchyRoomsChunk) {
self.set_room_id(Some(data.room_id));
self.set_alias(data.canonical_alias);
self.set_name(data.name);
self.set_topic(data.topic);
self.set_joined_members_count(data.num_joined_members.try_into().unwrap_or(u32::MAX));
if let Some(image) = self.obj().avatar_data().image() {
image.set_uri_and_info(data.avatar_url, None);
}
self.set_loading_state(LoadingState::Ready);
}
}
}
glib::wrapper! {
pub struct RemoteRoom(ObjectSubclass<imp::RemoteRoom>) @extends PillSource;
}
impl RemoteRoom {
pub fn new(session: &Session, uri: MatrixRoomIdUri) -> Self {
let obj = glib::Object::builder::<Self>()
.property("session", session)
.build();
let imp = obj.imp();
imp.uri.set(uri).unwrap();
imp.update_display_name();
spawn!(clone!(
#[weak]
obj,
async move {
obj.load().await;
}
));
obj
}
pub fn uri(&self) -> &MatrixRoomIdUri {
self.imp().uri.get().unwrap()
}
pub fn room_id(&self) -> Option<OwnedRoomId> {
self.imp()
.room_id
.borrow()
.clone()
.or_else(|| self.uri().id.clone().try_into().ok())
}
pub fn alias(&self) -> Option<OwnedRoomAliasId> {
self.imp()
.alias
.borrow()
.clone()
.or_else(|| self.uri().id.clone().try_into().ok())
}
async fn load(&self) {
let Some(session) = self.session() else {
return;
};
let imp = self.imp();
imp.set_loading_state(LoadingState::Loading);
let uri = self.uri();
let client = session.client();
let room_id = match OwnedRoomId::try_from(uri.id.clone()) {
Ok(room_id) => room_id,
Err(alias) => {
let client_clone = client.clone();
let handle =
spawn_tokio!(async move { client_clone.resolve_room_alias(&alias).await });
match handle.await.unwrap() {
Ok(response) => response.room_id,
Err(error) => {
warn!("Could not resolve room alias `{}`: {error}", uri.id);
imp.set_loading_state(LoadingState::Error);
return;
}
}
}
};
let request = assign!(get_hierarchy::v1::Request::new(room_id.clone()), {
limit: Some(uint!(1))
});
let handle = spawn_tokio!(async move { client.send(request).await });
match handle.await.unwrap() {
Ok(response) => {
if let Some(chunk) = response
.rooms
.into_iter()
.next()
.filter(|c| c.room_id == room_id)
{
imp.update_data(chunk);
} else {
debug!("Endpoint did not return requested room");
imp.set_loading_state(LoadingState::Error);
}
}
Err(error) => {
warn!("Could not get room details for room `{}`: {error}", uri.id);
imp.set_loading_state(LoadingState::Error);
}
}
}
}