[fractal/fractal-next] session: Implement session logout
- From: Julian Sparber <jsparber src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [fractal/fractal-next] session: Implement session logout
- Date: Mon, 11 Oct 2021 11:47:47 +0000 (UTC)
commit aea756afbe761da8aa8327c577fc6318a77385a0
Author: Julian Sparber <julian sparber net>
Date: Mon Oct 11 13:32:29 2021 +0200
session: Implement session logout
Currently the logout action isn't exposed to the user.
src/components/auth_dialog.rs | 2 +-
src/secret.rs | 44 +++++++++-
.../account_settings/devices_page/device.rs | 2 +-
.../account_settings/devices_page/device_list.rs | 2 +-
src/session/avatar.rs | 2 +-
src/session/content/explore/mod.rs | 2 +-
src/session/content/explore/public_room_list.rs | 2 +-
src/session/mod.rs | 96 +++++++++++++++++++---
src/session/room/mod.rs | 2 +-
src/session/room_creation/mod.rs | 2 +-
src/session/room_list.rs | 2 +-
src/window.rs | 17 ++++
12 files changed, 153 insertions(+), 22 deletions(-)
---
diff --git a/src/components/auth_dialog.rs b/src/components/auth_dialog.rs
index c2a06209..44ed2a71 100644
--- a/src/components/auth_dialog.rs
+++ b/src/components/auth_dialog.rs
@@ -275,7 +275,7 @@ impl AuthDialog {
if let Some(session) = uiaa_info.session {
priv_.stack.set_visible_child_name("fallback");
- let client = self.session().client().clone();
+ let client = self.session().client();
let (sender, receiver) = futures::channel::oneshot::channel();
RUNTIME.spawn(async move { sender.send(client.homeserver().await) });
let homeserver = receiver.await.unwrap();
diff --git a/src/secret.rs b/src/secret.rs
index e4021382..1bb89c2c 100644
--- a/src/secret.rs
+++ b/src/secret.rs
@@ -6,6 +6,7 @@ use std::convert::TryFrom;
use std::path::PathBuf;
use url::Url;
+#[derive(Debug, Clone)]
pub struct StoredSession {
pub homeserver: Url,
pub path: PathBuf,
@@ -80,7 +81,7 @@ pub fn restore_sessions() -> Result<Vec<StoredSession>, secret_service::Error> {
/// Writes a session to the `SecretService`, overwriting any previously stored session with the
/// same `homeserver`, `username` and `device-id`.
-pub fn store_session(session: StoredSession) -> Result<(), secret_service::Error> {
+pub fn store_session(session: &StoredSession) -> Result<(), secret_service::Error> {
let ss = SecretService::new(EncryptionType::Dh)?;
let collection = get_default_collection_unlocked(&ss)?;
@@ -124,6 +125,47 @@ pub fn store_session(session: StoredSession) -> Result<(), secret_service::Error
Ok(())
}
+/// Removes a session from the `SecretService`
+pub fn remove_session(session: &StoredSession) -> Result<(), secret_service::Error> {
+ let ss = SecretService::new(EncryptionType::Dh)?;
+ let collection = get_default_collection_unlocked(&ss)?;
+
+ // Store the information for the login
+ let attributes: HashMap<&str, &str> = [
+ ("user-id", session.user_id.as_str()),
+ ("homeserver", session.homeserver.as_str()),
+ ("device-id", session.device_id.as_str()),
+ ]
+ .iter()
+ .cloned()
+ .collect();
+
+ let items = collection.search_items(attributes)?;
+
+ for item in items {
+ item.delete()?;
+ }
+
+ // Store the information for the crypto store
+ let attributes: HashMap<&str, &str> = [
+ ("path", session.path.to_str().unwrap()),
+ ("user-id", session.user_id.as_str()),
+ ("homeserver", session.homeserver.as_str()),
+ ("device-id", session.device_id.as_str()),
+ ]
+ .iter()
+ .cloned()
+ .collect();
+
+ let items = collection.search_items(attributes)?;
+
+ for item in items {
+ item.delete()?;
+ }
+
+ Ok(())
+}
+
fn get_default_collection_unlocked<'a>(
secret_service: &'a SecretService,
) -> Result<secret_service::Collection<'a>, secret_service::Error> {
diff --git a/src/session/account_settings/devices_page/device.rs
b/src/session/account_settings/devices_page/device.rs
index acd16b84..8a38ea44 100644
--- a/src/session/account_settings/devices_page/device.rs
+++ b/src/session/account_settings/devices_page/device.rs
@@ -184,7 +184,7 @@ impl Device {
/// Returns `true` for success
pub async fn delete(&self, transient_for: Option<&impl IsA<gtk::Window>>) -> bool {
let session = self.session();
- let client = session.client().clone();
+ let client = session.client();
let device_id = self.device_id().to_owned();
let delete_fn = move |auth_data: Option<AuthData>| {
diff --git a/src/session/account_settings/devices_page/device_list.rs
b/src/session/account_settings/devices_page/device_list.rs
index 65396d87..154d086a 100644
--- a/src/session/account_settings/devices_page/device_list.rs
+++ b/src/session/account_settings/devices_page/device_list.rs
@@ -195,7 +195,7 @@ impl DeviceList {
}
pub fn load_devices(&self) {
- let client = self.session().client().clone();
+ let client = self.session().client();
self.set_loading(true);
diff --git a/src/session/avatar.rs b/src/session/avatar.rs
index d7c62ccc..4ab465ce 100644
--- a/src/session/avatar.rs
+++ b/src/session/avatar.rs
@@ -168,7 +168,7 @@ impl Avatar {
}
if let Some(url) = self.url() {
- let client = self.session().client().clone();
+ let client = self.session().client();
let request = MediaRequest {
media_type: MediaType::Uri(url),
format: MediaFormat::File,
diff --git a/src/session/content/explore/mod.rs b/src/session/content/explore/mod.rs
index a7de1fed..3040c7e2 100644
--- a/src/session/content/explore/mod.rs
+++ b/src/session/content/explore/mod.rs
@@ -229,7 +229,7 @@ impl Explore {
fn load_protocols(&self) {
let priv_ = imp::Explore::from_instance(self);
- let client = self.session().unwrap().client().clone();
+ let client = self.session().unwrap().client();
priv_.network_menu.remove_all();
priv_.network_menu.append(Some("matrix"), "Matrix");
diff --git a/src/session/content/explore/public_room_list.rs b/src/session/content/explore/public_room_list.rs
index 7d530d1d..9b59101e 100644
--- a/src/session/content/explore/public_room_list.rs
+++ b/src/session/content/explore/public_room_list.rs
@@ -284,7 +284,7 @@ impl PublicRoomList {
return;
}
- let client = self.session().unwrap().client().clone();
+ let client = self.session().unwrap().client();
let search_term = priv_.search_term.borrow().to_owned();
let server = priv_.server.borrow().to_owned();
let network = priv_.network.borrow().to_owned();
diff --git a/src/session/mod.rs b/src/session/mod.rs
index 3c4ffe00..c74e717c 100644
--- a/src/session/mod.rs
+++ b/src/session/mod.rs
@@ -20,19 +20,24 @@ pub use self::user::{User, UserExt};
use crate::secret;
use crate::secret::StoredSession;
use crate::utils::do_async;
+use crate::Error;
use crate::Window;
use crate::RUNTIME;
use crate::login::LoginError;
use crate::session::content::ContentType;
use adw::subclass::prelude::BinImpl;
+use gettextrs::gettext;
use gtk::subclass::prelude::*;
use gtk::{self, prelude::*};
use gtk::{gdk, glib, glib::clone, glib::SyncSender, CompositeTemplate, SelectionModel};
use gtk_macros::send;
use log::error;
use matrix_sdk::ruma::{
- api::client::r0::filter::{FilterDefinition, LazyLoadOptions, RoomEventFilter, RoomFilter},
+ api::client::r0::{
+ filter::{FilterDefinition, LazyLoadOptions, RoomEventFilter, RoomFilter},
+ session::logout,
+ },
assign,
};
use matrix_sdk::{
@@ -63,12 +68,13 @@ mod imp {
pub sidebar: TemplateChild<Sidebar>,
/// Contains the error if something went wrong
pub error: RefCell<Option<matrix_sdk::Error>>,
- pub client: OnceCell<Client>,
+ pub client: RefCell<Option<Client>>,
pub room_list: OnceCell<RoomList>,
pub user: OnceCell<User>,
pub selected_room: RefCell<Option<Room>>,
pub selected_content_type: Cell<ContentType>,
- pub is_ready: OnceCell<bool>,
+ pub is_ready: Cell<bool>,
+ pub info: OnceCell<StoredSession>,
}
#[glib::object_subclass]
@@ -84,6 +90,10 @@ mod imp {
session.set_selected_room(None);
});
+ klass.install_action("session.logout", None, move |session, _, _| {
+ session.logout();
+ });
+
klass.install_action("session.room-creation", None, move |session, _, _| {
session.show_room_creation_dialog();
});
@@ -190,7 +200,10 @@ mod imp {
fn signals() -> &'static [Signal] {
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
- vec![Signal::builder("prepared", &[], <()>::static_type().into()).build()]
+ vec![
+ Signal::builder("prepared", &[], <()>::static_type().into()).build(),
+ Signal::builder("logged-out", &[], <()>::static_type().into()).build(),
+ ]
});
SIGNALS.as_ref()
}
@@ -341,7 +354,7 @@ impl Session {
let priv_ = imp::Session::from_instance(self);
match result {
Ok((client, session)) => {
- priv_.client.set(client.clone()).unwrap();
+ priv_.client.replace(Some(client.clone()));
let user = User::new(self, &session.user_id);
priv_.user.set(user.clone()).unwrap();
self.notify("user");
@@ -366,9 +379,11 @@ impl Session {
if store_session {
// TODO: report secret service errors
- secret::store_session(session).unwrap();
+ secret::store_session(&session).unwrap();
}
+ priv_.info.set(session).unwrap();
+
self.room_list().load();
self.sync();
}
@@ -380,9 +395,8 @@ impl Session {
}
fn sync(&self) {
- let priv_ = imp::Session::from_instance(self);
let sender = self.create_new_sync_response_sender();
- let client = priv_.client.get().unwrap().clone();
+ let client = self.client();
RUNTIME.spawn(async move {
// TODO: only create the filter once and reuse it in the future
let room_event_filter = assign!(RoomEventFilter::default(), {
@@ -416,12 +430,12 @@ impl Session {
fn mark_ready(&self) {
let priv_ = &imp::Session::from_instance(self);
priv_.stack.set_visible_child(&*priv_.content);
- priv_.is_ready.set(true).unwrap();
+ priv_.is_ready.set(true);
}
fn is_ready(&self) -> bool {
let priv_ = &imp::Session::from_instance(self);
- priv_.is_ready.get().copied().unwrap_or_default()
+ priv_.is_ready.get()
}
pub fn room_list(&self) -> &RoomList {
@@ -434,9 +448,13 @@ impl Session {
priv_.user.get()
}
- pub fn client(&self) -> &Client {
+ pub fn client(&self) -> Client {
let priv_ = &imp::Session::from_instance(self);
- priv_.client.get().unwrap()
+ priv_
+ .client
+ .borrow()
+ .clone()
+ .expect("The session isn't ready")
}
/// Sets up the required channel to receive new room events
@@ -477,6 +495,17 @@ impl Session {
.unwrap()
}
+ pub fn connect_logged_out<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
+ self.connect_local("logged-out", true, move |values| {
+ let obj = values[0].get::<Self>().unwrap();
+
+ f(&obj);
+
+ None
+ })
+ .unwrap()
+ }
+
fn handle_sync_response(&self, response: SyncResponse) {
self.room_list().handle_response_rooms(response.rooms);
}
@@ -504,6 +533,49 @@ impl Session {
let window = RoomCreation::new(self.parent_window().as_ref(), self);
window.show();
}
+
+ pub fn logout(&self) {
+ let client = self.client();
+
+ do_async(
+ glib::PRIORITY_DEFAULT_IDLE,
+ async move {
+ let request = logout::Request::new();
+ client.send(request, None).await
+ },
+ clone!(@weak self as obj => move |result| async move {
+ match result {
+ Ok(_) => obj.cleanup_session(),
+ Err(error) => {
+ error!("Couldn’t logout the session {}", error);
+ let error = Error::new(
+ clone!(@weak obj => @default-return None, move |_| {
+ let label = gtk::Label::new(Some(&gettext("Failed to logout the
session.")));
+
+ Some(label.upcast())
+ }),
+ );
+
+ if let Some(window) = obj.parent_window() {
+ window.append_error(&error);
+ }
+ }
+ }
+ }),
+ );
+ }
+
+ fn cleanup_session(&self) {
+ let priv_ = &imp::Session::from_instance(self);
+ let info = priv_.info.get().unwrap();
+
+ priv_.is_ready.set(false);
+ secret::remove_session(info).unwrap();
+ priv_.client.take();
+ fs::remove_dir_all(info.path.clone()).unwrap();
+
+ self.emit_by_name("logged-out", &[]).unwrap();
+ }
}
impl Default for Session {
diff --git a/src/session/room/mod.rs b/src/session/room/mod.rs
index 4600c79d..68a58ed2 100644
--- a/src/session/room/mod.rs
+++ b/src/session/room/mod.rs
@@ -869,7 +869,7 @@ impl Room {
/// Removes the avatar if no filename is given.
pub fn store_avatar(&self, filename: Option<PathBuf>) {
let matrix_room = self.matrix_room();
- let client = self.session().client().clone();
+ let client = self.session().client();
do_async(
glib::PRIORITY_DEFAULT_IDLE,
diff --git a/src/session/room_creation/mod.rs b/src/session/room_creation/mod.rs
index 7ab1f462..239e2e99 100644
--- a/src/session/room_creation/mod.rs
+++ b/src/session/room_creation/mod.rs
@@ -206,7 +206,7 @@ impl RoomCreation {
priv_.cancel_button.set_sensitive(false);
priv_.error_label_revealer.set_reveal_child(false);
- let client = self.session()?.client().clone();
+ let client = self.session()?.client();
let room_name = priv_.room_name.text().to_string();
diff --git a/src/session/room_list.rs b/src/session/room_list.rs
index eae50a0a..21c91719 100644
--- a/src/session/room_list.rs
+++ b/src/session/room_list.rs
@@ -314,7 +314,7 @@ impl RoomList {
}
pub fn join_by_id_or_alias(&self, identifier: RoomIdOrAliasId) {
- let client = self.session().client().clone();
+ let client = self.session().client();
let identifier_clone = identifier.clone();
self.pending_rooms_insert(identifier.clone());
diff --git a/src/window.rs b/src/window.rs
index 538698b2..af118dd9 100644
--- a/src/window.rs
+++ b/src/window.rs
@@ -71,6 +71,7 @@ mod imp {
self.main_stack.connect_visible_child_notify(
clone!(@weak obj => move |_| obj.set_default_by_child()),
);
+
obj.set_default_by_child();
}
}
@@ -108,6 +109,22 @@ impl Window {
priv_.sessions.set_visible_child(session);
// We need to grab the focus so that keyboard shortcuts work
session.grab_focus();
+
+ session.connect_logged_out(clone!(@weak self as obj => move |session| {
+ obj.remove_session(session)
+ }));
+ }
+
+ fn remove_session(&self, session: &Session) {
+ let priv_ = imp::Window::from_instance(self);
+
+ priv_.sessions.remove(session);
+
+ if let Some(child) = priv_.sessions.first_child() {
+ priv_.sessions.set_visible_child(&child);
+ } else {
+ self.switch_to_login_page();
+ }
}
fn restore_sessions(&self) {
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]