fractal/session/model/user_sessions_list/
user_session.rsuse gtk::{glib, prelude::*, subclass::prelude::*};
use matrix_sdk::encryption::identities::Device as CryptoDevice;
use ruma::{
api::client::device::{delete_device, Device as DeviceData},
assign, DeviceId, OwnedDeviceId,
};
use tracing::{debug, error};
use crate::{
components::{AuthDialog, AuthError},
prelude::*,
session::model::Session,
};
#[derive(Debug, Clone)]
pub enum UserSessionData {
DeviceId(OwnedDeviceId),
DevicesApi(DeviceData),
Crypto(CryptoDevice),
Both {
api: DeviceData,
crypto: CryptoDevice,
},
}
impl UserSessionData {
pub fn device_id(&self) -> &DeviceId {
match self {
UserSessionData::DeviceId(device_id) => device_id,
UserSessionData::DevicesApi(api) | UserSessionData::Both { api, .. } => &api.device_id,
UserSessionData::Crypto(crypto) => crypto.device_id(),
}
}
pub fn api(&self) -> Option<&DeviceData> {
match self {
UserSessionData::DevicesApi(api) | UserSessionData::Both { api, .. } => Some(api),
UserSessionData::DeviceId(_) | UserSessionData::Crypto(_) => None,
}
}
pub fn crypto(&self) -> Option<&CryptoDevice> {
match self {
UserSessionData::Crypto(crypto) | UserSessionData::Both { crypto, .. } => Some(crypto),
UserSessionData::DeviceId(_) | UserSessionData::DevicesApi(_) => None,
}
}
}
mod imp {
use std::{
cell::{Cell, OnceCell},
marker::PhantomData,
};
use super::*;
#[derive(Debug, Default, glib::Properties)]
#[properties(wrapper_type = super::UserSession)]
pub struct UserSession {
#[property(get, construct_only)]
session: glib::WeakRef<Session>,
data: OnceCell<UserSessionData>,
#[property(get)]
is_current: Cell<bool>,
#[property(get = Self::device_id)]
device_id: PhantomData<String>,
#[property(get = Self::display_name)]
display_name: PhantomData<String>,
#[property(get = Self::last_seen_ip)]
last_seen_ip: PhantomData<Option<String>>,
#[property(get = Self::last_seen_ts)]
last_seen_ts: PhantomData<Option<glib::DateTime>>,
#[property(get = Self::verified)]
verified: PhantomData<bool>,
}
#[glib::object_subclass]
impl ObjectSubclass for UserSession {
const NAME: &'static str = "UserSession";
type Type = super::UserSession;
}
#[glib::derived_properties]
impl ObjectImpl for UserSession {}
impl UserSession {
pub(super) fn set_data(&self, data: UserSessionData) {
if let Some(session) = self.session.upgrade() {
let is_current = *session.device_id() == data.device_id();
self.is_current.set(is_current);
}
self.data.set(data).expect("data is uninitialized");
}
pub(super) fn data(&self) -> &UserSessionData {
self.data.get().expect("data is initialized")
}
fn device_id(&self) -> String {
self.data().device_id().to_string()
}
fn display_name(&self) -> String {
if let Some(display_name) = self.data().api().and_then(|d| d.display_name.clone()) {
display_name
} else {
self.device_id()
}
}
fn last_seen_ip(&self) -> Option<String> {
self.data().api()?.last_seen_ip.clone()
}
fn last_seen_ts(&self) -> Option<glib::DateTime> {
self.data().api()?.last_seen_ts.map(|last_seen_ts| {
glib::DateTime::from_unix_utc(last_seen_ts.as_secs().into())
.and_then(|t| t.to_local())
.expect("constructing GDateTime works")
})
}
fn verified(&self) -> bool {
self.data().crypto().is_some_and(|d| d.is_verified())
}
}
}
glib::wrapper! {
pub struct UserSession(ObjectSubclass<imp::UserSession>);
}
impl UserSession {
pub fn new(session: &Session, data: UserSessionData) -> Self {
let obj = glib::Object::builder::<Self>()
.property("session", session)
.build();
obj.imp().set_data(data);
obj
}
pub async fn delete(&self, parent: &impl IsA<gtk::Widget>) -> Result<(), AuthError> {
let Some(session) = self.session() else {
return Err(AuthError::NoSession);
};
let device_id = self.imp().data().device_id().to_owned();
let dialog = AuthDialog::new(&session);
let res = dialog
.authenticate(parent, move |client, auth| {
let device_id = device_id.clone();
async move {
let request = assign!(delete_device::v3::Request::new(device_id), { auth });
client.send(request, None).await.map_err(Into::into)
}
})
.await;
match res {
Ok(_) => Ok(()),
Err(error) => {
let device_id = self.device_id();
if matches!(error, AuthError::UserCancelled) {
debug!("Deletion of user session {device_id} cancelled by user");
} else {
error!("Could not delete user session {device_id}: {error:?}");
}
Err(error)
}
}
}
}