[fractal/fractal-next] session: Implement session logout



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]