Browse Source

user: Refactor and clean up

merge-requests/2003/head
Kévin Commaille 11 months ago
parent
commit
0cc9ed3ae9
No known key found for this signature in database
GPG Key ID: C971D9DBC9D678D
  1. 4
      src/components/user_page.rs
  2. 52
      src/session/model/remote/user.rs
  3. 216
      src/session/model/user.rs
  4. 2
      src/session/view/content/room_details/member_row.ui
  5. 2
      src/session/view/create_direct_chat_dialog/user_list.rs

4
src/components/user_page.rs

@ -133,7 +133,7 @@ mod imp {
.build();
let bindings = vec![title_binding, avatar_binding];
let verified_handler = user.connect_verified_notify(clone!(
let verified_handler = user.connect_is_verified_notify(clone!(
#[weak(rename_to = imp)]
self,
move |_| {
@ -671,7 +671,7 @@ mod imp {
return;
};
if user.verified() {
if user.is_verified() {
self.verified_row.set_title(&gettext("Identity verified"));
self.verified_stack.set_visible_child_name("icon");
self.verify_button.set_sensitive(false);

52
src/session/model/remote/user.rs

@ -2,14 +2,13 @@ use std::time::{Duration, Instant};
use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*};
use matrix_sdk::ruma::OwnedUserId;
use tracing::error;
use crate::{
components::PillSource,
prelude::*,
session::model::{Session, User},
spawn, spawn_tokio,
utils::{AbortableHandle, LoadingState},
spawn,
utils::LoadingState,
};
/// The time after which the profile of a user is assumed to be stale.
@ -30,7 +29,6 @@ mod imp {
loading_state: Cell<LoadingState>,
// The time of the last request.
last_request_time: Cell<Option<Instant>>,
request_abort_handle: AbortableHandle,
}
#[glib::object_subclass]
@ -77,39 +75,6 @@ mod imp {
pub(super) fn update_last_request_time(&self) {
self.last_request_time.set(Some(Instant::now()));
}
/// Request the profile of this user.
pub(super) async fn load_profile(&self) {
let obj = self.obj();
self.set_loading_state(LoadingState::Loading);
let user_id = obj.user_id();
let client = obj.session().client();
let user_id_clone = user_id.clone();
let handle = spawn_tokio!(async move {
client.account().fetch_user_profile_of(&user_id_clone).await
});
let Some(result) = self.request_abort_handle.await_task(handle).await else {
// The task was aborted, which means that the object was dropped.
return;
};
let profile = match result {
Ok(profile) => profile,
Err(error) => {
error!("Could not load profile for user `{user_id}`: {error}");
self.set_loading_state(LoadingState::Error);
return;
}
};
obj.set_name(profile.displayname);
obj.set_avatar_url(profile.avatar_url);
self.set_loading_state(LoadingState::Ready);
}
}
}
@ -145,10 +110,17 @@ impl RemoteUser {
imp.update_last_request_time();
spawn!(clone!(
#[weak]
imp,
#[weak(rename_to = obj)]
self,
async move {
imp.load_profile().await;
let imp = obj.imp();
imp.set_loading_state(LoadingState::Loading);
let loading_state = match obj.load_profile().await {
Ok(()) => LoadingState::Ready,
Err(()) => LoadingState::Error,
};
imp.set_loading_state(loading_state);
}
));
}

216
src/session/model/user.rs

@ -1,11 +1,6 @@
use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*};
use matrix_sdk::encryption::identities::UserIdentity;
use ruma::{
api::client::room::create_room,
assign,
events::{room::encryption::RoomEncryptionEventContent, InitialStateEvent},
MatrixToUri, OwnedMxcUri, OwnedUserId,
};
use ruma::{MatrixToUri, OwnedMxcUri, OwnedUserId};
use tracing::{debug, error};
use super::{IdentityVerification, Room, Session};
@ -38,22 +33,22 @@ mod imp {
#[properties(wrapper_type = super::User)]
pub struct User {
/// The ID of this user.
pub user_id: OnceCell<OwnedUserId>,
user_id: OnceCell<OwnedUserId>,
/// The ID of this user, as a string.
#[property(get = Self::user_id_string)]
pub user_id_string: PhantomData<String>,
user_id_string: PhantomData<String>,
/// The current session.
#[property(get, construct_only)]
pub session: OnceCell<Session>,
session: OnceCell<Session>,
/// Whether this user is the same as the session's user.
#[property(get)]
pub is_own_user: Cell<bool>,
is_own_user: Cell<bool>,
/// Whether this user has been verified.
#[property(get)]
pub verified: Cell<bool>,
is_verified: Cell<bool>,
/// Whether this user is currently ignored.
#[property(get)]
pub is_ignored: Cell<bool>,
is_ignored: Cell<bool>,
ignored_handler: RefCell<Option<glib::SignalHandlerId>>,
}
@ -90,13 +85,23 @@ mod imp {
}
impl User {
/// The ID of this user.
pub(super) fn user_id(&self) -> &OwnedUserId {
self.user_id.get().expect("user ID should be initialized")
}
/// The ID of this user, as a string.
fn user_id_string(&self) -> String {
self.user_id.get().unwrap().to_string()
self.user_id().to_string()
}
/// The current session.
fn session(&self) -> &Session {
self.session.get().expect("session should be initialized")
}
/// Set the ID of this user.
pub fn set_user_id(&self, user_id: OwnedUserId) {
pub(crate) fn set_user_id(&self, user_id: OwnedUserId) {
let user_id = self.user_id.get_or_init(|| user_id);
let obj = self.obj();
@ -105,7 +110,7 @@ mod imp {
.sync_create()
.build();
let session = self.session.get().expect("session is initialized");
let session = self.session();
self.is_own_user.set(session.user_id() == user_id);
let ignored_users = session.ignored_users();
@ -125,7 +130,71 @@ mod imp {
self.is_ignored.set(ignored_users.contains(user_id));
self.ignored_handler.replace(Some(ignored_handler));
obj.init_is_verified();
spawn!(clone!(
#[weak(rename_to = imp)]
self,
async move {
imp.init_is_verified().await;
}
));
}
/// Get the local cryptographic identity (aka cross-signing identity) of
/// this user.
///
/// Locally, we should always have the crypto identity of our own user
/// and of users with whom we share an encrypted room.
pub(super) async fn local_crypto_identity(&self) -> Option<UserIdentity> {
let encryption = self.session().client().encryption();
let user_id = self.user_id().clone();
let handle = spawn_tokio!(async move { encryption.get_user_identity(&user_id).await });
match handle.await.expect("task was not aborted") {
Ok(identity) => identity,
Err(error) => {
error!("Could not get local crypto identity: {error}");
None
}
}
}
/// Load whether this user is verified.
async fn init_is_verified(&self) {
// If a user is verified, we should have their crypto identity locally.
let is_verified = self
.local_crypto_identity()
.await
.is_some_and(|i| i.is_verified());
if self.is_verified.get() == is_verified {
return;
}
self.is_verified.set(is_verified);
self.obj().notify_is_verified();
}
/// Create an encrypted direct chat with this user.
pub(super) async fn create_direct_chat(&self) -> Result<Room, matrix_sdk::Error> {
let user_id = self.user_id().clone();
let client = self.session().client();
let handle = spawn_tokio!(async move { client.create_dm(&user_id).await });
match handle.await.expect("task was not aborted") {
Ok(matrix_room) => {
let room = self
.session()
.room_list()
.get_wait(matrix_room.room_id())
.await
.expect("The newly created room was not found");
Ok(room)
}
Err(error) => {
error!("Could not create direct chat: {error}");
Err(error)
}
}
}
}
}
@ -146,35 +215,13 @@ impl User {
obj
}
/// Get the local cryptographic identity (aka cross-signing identity) of
/// this user.
///
/// Locally, we should always have the crypto identity of our own user and
/// of users with whom we share an encrypted room.
///
/// To get the crypto identity of a user with whom we do not share an
/// encrypted room, use [`Self::ensure_crypto_identity()`].
pub async fn local_crypto_identity(&self) -> Option<UserIdentity> {
let encryption = self.session().client().encryption();
let user_id = self.user_id().clone();
let handle = spawn_tokio!(async move { encryption.get_user_identity(&user_id).await });
match handle.await.unwrap() {
Ok(identity) => identity,
Err(error) => {
error!("Could not get local crypto identity: {error}");
None
}
}
}
/// Get the cryptographic identity (aka cross-signing identity) of this
/// user.
///
/// First, we try to get the local crypto identity if we are sure that it is
/// up-to-date. If we do not have the crypto identity locally, we request it
/// from the homeserver.
pub async fn ensure_crypto_identity(&self) -> Option<UserIdentity> {
pub(crate) async fn ensure_crypto_identity(&self) -> Option<UserIdentity> {
let session = self.session();
let encryption = session.client().encryption();
let user_id = self.user_id();
@ -190,7 +237,7 @@ impl User {
let encryption_clone = encryption.clone();
let handle = spawn_tokio!(async move { encryption_clone.tracked_users().await });
match handle.await.unwrap() {
match handle.await.expect("task was not aborted") {
Ok(tracked_users) => tracked_users.contains(user_id),
Err(error) => {
error!("Could not get tracked users: {error}");
@ -202,7 +249,7 @@ impl User {
// Try to get the local crypto identity.
if should_have_local {
if let Some(identity) = self.local_crypto_identity().await {
if let Some(identity) = self.imp().local_crypto_identity().await {
return Some(identity);
}
}
@ -212,7 +259,7 @@ impl User {
let handle =
spawn_tokio!(async move { encryption.request_user_identity(&user_id_clone).await });
match handle.await.unwrap() {
match handle.await.expect("task was not aborted") {
Ok(identity) => identity,
Err(error) => {
error!("Could not request remote crypto identity: {error}");
@ -222,79 +269,25 @@ impl User {
}
/// Start a verification of the identity of this user.
pub async fn verify_identity(&self) -> Result<IdentityVerification, ()> {
pub(crate) async fn verify_identity(&self) -> Result<IdentityVerification, ()> {
self.session()
.verification_list()
.create(Some(self.clone()))
.await
}
/// Load whether this user is verified.
fn init_is_verified(&self) {
spawn!(clone!(
#[weak(rename_to = obj)]
self,
async move {
// If a user is verified, we should have their crypto identity locally.
let verified = obj
.local_crypto_identity()
.await
.is_some_and(|i| i.is_verified());
if verified == obj.verified() {
return;
}
obj.imp().verified.set(verified);
obj.notify_verified();
}
));
}
/// The existing direct chat with this user, if any.
///
/// A direct chat is a joined room marked as direct, with only our own user
/// and the other user in it.
pub fn direct_chat(&self) -> Option<Room> {
pub(crate) fn direct_chat(&self) -> Option<Room> {
self.session().room_list().direct_chat(self.user_id())
}
/// Create an encrypted direct chat with this user.
async fn create_direct_chat(&self) -> Result<Room, matrix_sdk::Error> {
let request = assign!(create_room::v3::Request::new(),
{
is_direct: true,
invite: vec![self.user_id().clone()],
preset: Some(create_room::v3::RoomPreset::TrustedPrivateChat),
initial_state: vec![
InitialStateEvent::new(RoomEncryptionEventContent::with_recommended_defaults()).to_raw_any(),
],
});
let client = self.session().client();
let handle = spawn_tokio!(async move { client.create_room(request).await });
match handle.await.unwrap() {
Ok(matrix_room) => {
let room = self
.session()
.room_list()
.get_wait(matrix_room.room_id())
.await
.expect("The newly created room was not found");
Ok(room)
}
Err(error) => {
error!("Could not create direct chat: {error}");
Err(error)
}
}
}
/// Get or create a direct chat with this user.
///
/// If there is no existing direct chat, a new one is created.
pub async fn get_or_create_direct_chat(&self) -> Result<Room, ()> {
pub(crate) async fn get_or_create_direct_chat(&self) -> Result<Room, ()> {
let user_id = self.user_id();
if let Some(room) = self.direct_chat() {
@ -303,16 +296,16 @@ impl User {
}
debug!("Creating direct chat with {user_id}…");
self.create_direct_chat().await.map_err(|_| ())
self.imp().create_direct_chat().await.map_err(|_| ())
}
/// Ignore this user.
pub async fn ignore(&self) -> Result<(), ()> {
pub(crate) async fn ignore(&self) -> Result<(), ()> {
self.session().ignored_users().add(self.user_id()).await
}
/// Stop ignoring this user.
pub async fn stop_ignoring(&self) -> Result<(), ()> {
pub(crate) async fn stop_ignoring(&self) -> Result<(), ()> {
self.session().ignored_users().remove(self.user_id()).await
}
}
@ -325,7 +318,7 @@ pub trait UserExt: IsA<User> {
/// The ID of this user.
fn user_id(&self) -> &OwnedUserId {
self.upcast_ref().imp().user_id.get().unwrap()
self.upcast_ref().imp().user_id()
}
/// Whether this user is the same as the session's user.
@ -351,7 +344,7 @@ pub trait UserExt: IsA<User> {
self.upcast_ref()
.avatar_data()
.image()
.unwrap()
.expect("avatar data should have an image")
// User avatars never have information.
.set_uri_and_info(uri, None);
}
@ -364,7 +357,7 @@ pub trait UserExt: IsA<User> {
/// Load the user profile from the homeserver.
///
/// This overwrites the already loaded display name and avatar.
async fn load_profile(&self) {
async fn load_profile(&self) -> Result<(), ()> {
let user_id = self.user_id();
let client = self.session().client();
@ -374,15 +367,17 @@ pub trait UserExt: IsA<User> {
async move { client.account().fetch_user_profile_of(&user_id_clone).await }
);
match handle.await.unwrap() {
match handle.await.expect("task was not aborted") {
Ok(response) => {
let user = self.upcast_ref::<User>();
user.set_name(response.displayname);
user.set_avatar_url(response.avatar_url);
Ok(())
}
Err(error) => {
error!("Could not load user profile for {user_id}: {error}");
error!("Could not load user profile for `{user_id}`: {error}");
Err(())
}
}
}
@ -394,8 +389,11 @@ pub trait UserExt: IsA<User> {
/// Connect to the signal emitted when the `is-ignored` property changes.
fn connect_is_ignored_notify<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
self.upcast_ref()
.connect_is_ignored_notify(move |user| f(user.downcast_ref().unwrap()))
self.upcast_ref().connect_is_ignored_notify(move |user| {
f(user
.downcast_ref()
.expect("downcasting to own type should succeed"));
})
}
}

2
src/session/view/content/room_details/member_row.ui

@ -44,7 +44,7 @@
<property name="icon-name">verified-symbolic</property>
<property name="tooltip-text" translatable="yes">Identity Verified</property>
<binding name="visible">
<lookup name="verified" type="User">
<lookup name="is-verified" type="User">
<lookup name="member">ContentMemberRow</lookup>
</lookup>
</binding>

2
src/session/view/create_direct_chat_dialog/user_list.rs

@ -170,7 +170,7 @@ mod imp {
#[weak]
user,
async move {
user.load_profile().await;
let _ = user.load_profile().await;
}
));

Loading…
Cancel
Save